diff --git a/Dockerfile b/Dockerfile index 48e31fa894f4590754059137bc260680b83c92a8..379e1b3a71fddfcf96f7c06ca00e4fec5f31eff4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,23 +1,34 @@ -FROM python:3.9 +# syntax=docker/dockerfile:1 +FROM python:3.8 -RUN pip install --user --upgrade pip -RUN apt-get update && apt-get install -y ffmpeg +RUN apt-get update && \ + apt-get install -y ffmpeg jq curl && \ + pip install --upgrade pip -COPY . . +WORKDIR /app +COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -RUN mkdir -p /tmq -RUN chmod 777 /tmp -RUN mkdir -p /.cache -RUN chmod 777 /.cache +COPY scripts/ . +COPY app ./app +copy img ./img + +RUN wget --progress=bar:force:noscroll https://huggingface.co/fabiogra/baseline_vocal_remover/resolve/main/baseline.pth + +RUN mkdir -p /tmp/ /tmp/vocal_remover /.cache /.config && \ + chmod 777 /tmp /tmp/vocal_remover /.cache /.config ENV PYTHONPATH "${PYTHONPATH}:/app" +RUN chmod +x prepare_samples.sh + EXPOSE 7860 HEALTHCHECK CMD curl --fail http://localhost:7860/_stcore/health -ENTRYPOINT ["streamlit", "run", "app/main.py", "--server.port=7860", "--server.address=0.0.0.0"] \ No newline at end of file +RUN ["./prepare_samples.sh"] + +ENTRYPOINT ["streamlit", "run", "app/header.py", "--server.port=7860", "--server.address=0.0.0.0"] diff --git a/README.md b/README.md index ad33575c99b1fe2ff8872687fba9e34e7684dfaf..1fb233328bf905c41a7e9c0d6047a5a9fe313baa 100644 --- a/README.md +++ b/README.md @@ -1,37 +1,210 @@ --- -title: Music Splitter -emoji: 🎶 -colorFrom: indigo -colorTo: yellow +title: Test Moseca +emoji: 🎤🎸🥁🎹 +colorFrom: yellow +colorTo: purple sdk: docker +app_port: 7860 +models: ["https://huggingface.co/fabiogra/baseline_vocal_remover"] +tags: ["audio", "music", "vocal-removal", "karaoke", "music-separation", "music-source-separation"] pinned: true --- -Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference +
+ +
+Music Source Separation & Karaoke
-# Music Source Splitter 🎶 - + + + + + + -This is a streamlit demo of the [Music Source Separation](https://huggingface.co/spaces/fabiogra/st-music-splitter). +--- + +- [Setup](#setup) +- [About](#about) + - [High-Quality Stem Separation](#high-quality-stem-separation) + - [Advanced AI Algorithms](#advanced-ai-algorithms) + - [Karaoke Fun](#karaoke-fun) + - [Easy Deployment](#easy-deployment) + - [Open-Source and Free](#open-source-and-free) + - [Support](#support) +- [FAQs](#faqs) + - [What is Moseca?](#what-is-moseca) + - [Are there any limitations?](#are-there-any-limitations) + - [How does Moseca work?](#how-does-moseca-work) + - [How do I use Moseca?](#how-do-i-use-moseca) + - [Where can I find the code for Moseca?](#where-can-i-find-the-code-for-moseca) + - [How can I get in touch with you?](#how-can-i-get-in-touch-with-you) +- [Disclaimer](#disclaimer) -The model can separate the vocals, drums, bass, and other from a music track. -## Usage +--- -You can use the demo [here](https://huggingface.co/spaces/fabiogra/st-music-splitter), or run it locally with: +## Setup +### Local environment +Create a new environment with Python 3.8 and install the requirements: +```bash +pip install -r requirements.txt +``` +then run the app with: +```bash +streamlit run app/header.py +``` +### Docker +You can also run the app with Docker: ```bash -streamlit run app.py +docker build -t moseca . +docker run -it --rm -p 7860:7860 $(DOCKER_IMAGE_NAME) ``` -> **Note**: In order to run the demo locally, you need to install the dependencies with `pip install -r requirements.txt`. +or pull the image from Hugging Face Spaces: +```bash +docker run -it -p 7860:7860 --platform=linux/amd64 \ + registry.hf.space/fabiogra-moseca:latest +``` + +You can set the following environment variables to limit the resources used by the app: +- ENV_LIMITATION=true +- LIMIT_CPU=true +--- +## About + +Welcome to Moseca, your personal web application designed to redefine your music experience. +Whether you're a musician looking to remix your favorite songs, a karaoke +enthusiast, or a music lover wanting to dive deeper into your favorite tracks, +Moseca is for you. + +Music Source Separation & Karaoke
", + unsafe_allow_html=True, + ) + + +if __name__ == "__main__": + header() diff --git a/app/helpers.py b/app/helpers.py index 2a02e10dbc2d825778f40611d8b4c4962243fc63..195d02157cf47e5adb9804afab5e344eb23df057 100644 --- a/app/helpers.py +++ b/app/helpers.py @@ -1,19 +1,160 @@ -from pydub import AudioSegment +import random +from io import BytesIO +import json -import streamlit as st -import plotly.graph_objs as go -import plotly.express as px -import pandas as pd +import matplotlib.pyplot as plt import numpy as np +import requests +import streamlit as st +from PIL import Image +from pydub import AudioSegment +from base64 import b64encode +from pathlib import Path +from streamlit.runtime.scriptrunner import RerunData, RerunException +from streamlit.source_util import get_pages +from streamlit_player import st_player + +extensions = ["mp3", "wav", "ogg", "flac"] # we will look for all those file types. +example_songs = [1, 2, 3] + + +def img_to_bytes(img_path): + img_bytes = Path(img_path).read_bytes() + encoded = b64encode(img_bytes).decode() + return encoded + + +# @st.cache_data(show_spinner=False) +def img_to_html(img_path): + img_html = "".format( + img_to_bytes(img_path) + ) + return img_html -@st.cache_data + +@st.cache_data(show_spinner=False) +def url_is_valid(url): + if url.startswith("http") is False: + st.error("URL should start with http or https.") + return False + elif url.split(".")[-1] not in extensions: + st.error("Extension not supported.") + return False + try: + r = requests.get(url) + r.raise_for_status() + return True + except Exception: + st.error("URL is not valid.") + return False + + +@st.cache_data(show_spinner=False) def load_audio_segment(path: str, format: str) -> AudioSegment: return AudioSegment.from_file(path, format=format) -def plot_audio(_audio_segment: AudioSegment, title: str = None, step = 20) -> go.Figure: + +@st.cache_data(show_spinner=False) +def plot_audio(_audio_segment: AudioSegment, *args, **kwargs) -> Image.Image: samples = _audio_segment.get_array_of_samples() - arr = np.array(samples[::step]) - df = pd.DataFrame(arr) - fig = px.line(df, y=0, render_mode="webgl", line_shape="linear", width=1000, height=60, title=title) - fig.update_layout(xaxis_fixedrange=True, yaxis_fixedrange=True, yaxis_visible=False, xaxis_visible=False, hovermode=False, margin=dict(l=0, r=0, t=0, b=0)) - st.plotly_chart(fig, use_container_width=True) + arr = np.array(samples) + + fig, ax = plt.subplots(figsize=(10, 2)) + ax.plot(arr, linewidth=0.05) + ax.set_axis_off() + + # Set the background color to transparent + fig.patch.set_alpha(0) + ax.patch.set_alpha(0) + + buf = BytesIO() + plt.savefig(buf, format="png", dpi=100, bbox_inches="tight") + buf.seek(0) + image = Image.open(buf) + + plt.close(fig) + return image + + +def get_random_song(): + sample_songs = json.load(open("sample_songs.json")) + name, url = random.choice(list(sample_songs.items())) + return name, url + + +def streamlit_player( + player, + url, + height, + is_active, + muted, + start, + key, + playback_rate=1, + events=None, + play_inline=False, + light=False, +): + with player: + options = { + "progress_interval": 1000, + "playing": is_active, # st.checkbox("Playing", False), + "muted": muted, + "light": light, + "play_inline": play_inline, + "playback_rate": playback_rate, + "height": height, + "config": {"start": start}, + "events": events, + } + if url != "": + events = st_player(url, **options, key=key) + return events + + +@st.cache_data(show_spinner=False) +def local_audio(path, mime="audio/mp3"): + data = b64encode(Path(path).read_bytes()).decode() + return [{"type": mime, "src": f"data:{mime};base64,{data}"}] + + +def _standardize_name(name: str) -> str: + return name.lower().replace("_", " ").strip() + + +@st.cache_data(show_spinner=False) +def switch_page(page_name: str): + st.session_state.page = page_name + + page_name = _standardize_name(page_name) + + pages = get_pages("header.py") # OR whatever your main page is called + + for page_hash, config in pages.items(): + if _standardize_name(config["page_name"]) == page_name: + raise RerunException( + RerunData( + page_script_hash=page_hash, + page_name=page_name, + ) + ) + + page_names = [_standardize_name(config["page_name"]) for config in pages.values()] + raise ValueError(f"Could not find page {page_name}. Must be one of {page_names}") + + +def st_local_audio(pathname, key): + st_player( + local_audio(pathname), + **{ + "progress_interval": 1000, + "playing": False, + "muted": False, + "light": False, + "play_inline": True, + "playback_rate": 1, + "height": 40, + "config": {"start": 0, "forceAudio": True, "forceHLS": True, "forceSafariHLS": True}, + }, + key=key, + ) diff --git a/app/main.py b/app/main.py deleted file mode 100644 index e1e6be4b6c100fe471e3a5c898e47c44eeb24806..0000000000000000000000000000000000000000 --- a/app/main.py +++ /dev/null @@ -1,155 +0,0 @@ -import logging -import os -from pathlib import Path - -import requests -import streamlit as st -from app.examples import show_examples - -from demucs_runner import separator -from lib.st_custom_components import st_audiorec -from helpers import load_audio_segment, plot_audio -from sidebar import text as text_side - -logging.basicConfig( - format="%(asctime)s %(levelname)-8s %(message)s", - level=logging.DEBUG, - datefmt="%Y-%m-%d %H:%M:%S", -) - -max_duration = 10 # in seconds - -model = "htdemucs" -extensions = ["mp3", "wav", "ogg", "flac"] # we will look for all those file types. -two_stems = None # only separate one stems from the rest, for instance - -# Options for the output audio. -mp3 = True -mp3_rate = 320 -float32 = False # output as float 32 wavs, unsused if 'mp3' is True. -int24 = False # output as int24 wavs, unused if 'mp3' is True. -# You cannot set both `float32 = True` and `int24 = True` !! - - -out_path = Path("/tmp") -in_path = Path("/tmp") - - -def url_is_valid(url): - if url.startswith("http") is False: - st.error("URL should start with http or https.") - return False - elif url.split(".")[-1] not in extensions: - st.error("Extension not supported.") - return False - try: - r = requests.get(url) - r.raise_for_status() - return True - except Exception: - st.error("URL is not valid.") - return False - - -def run(): - st.markdown("