safi-space / app.py
Abdo96's picture
Upload 4 files
4706677 verified
Raw
History Blame Contribute Delete
9.69 kB
"""
ุตุงููŠ โ€” Hugging Face Space (ZeroGPU)
ูุตู„ ุงู„ู…ูˆุณูŠู‚ู‰ ุนู† ุงู„ูƒู„ุงู… ุจู…ูˆุฏูŠู„ Mel-Band RoFormer (Kim) ุนู„ู‰ GPU ู…ุฌุงู†ูŠ.
โ€ข ูˆุงุฌู‡ุฉ Gradio (ุจูŠุณุชุฎุฏู…ู‡ุง ุงู„ู†ุงุณ ู…ุจุงุดุฑุฉ)
โ€ข ูˆูƒู…ุงู† API ุชู„ู‚ุงุฆูŠ (ูˆุงุฌู‡ุฉ Cloudflare ุชู‚ุฏุฑ ุชู†ุงุฏูŠู‡)
ู…ู„ุงุญุธุงุช ZeroGPU ุงู„ู…ุชู‘ุจุนุฉ ู‡ู†ุง:
- import spaces + @spaces.GPU ุนู„ู‰ ุฏุงู„ุฉ ุงู„ูุตู„ ุจุณ
- ุงู„ู…ูˆุฏูŠู„ ุจูŠุชุญู…ู‘ู„ ู…ุฑุฉ ูˆุงุญุฏุฉ ููŠ ุงู„ู€ global (read-only) โ€” ู…ุด ุฌูˆู‘ู‡ ุงู„ุฏุงู„ุฉ
- ู…ููŠุด ุญุงู„ุฉ ุนุงู…ุฉ ู‚ุงุจู„ุฉ ู„ู„ุชุนุฏูŠู„ุŒ ูˆู…ุณุงุฑุงุช ุงู„ุฅุฎุฑุงุฌ ูุฑูŠุฏุฉ (tempfile) ู„ุฃู†
ุงู„ุทู„ุจุงุช ุจุชุดุชุบู„ ุจุงู„ุชูˆุงุฒูŠ ุงูุชุฑุงุถูŠุงู‹
- ู…ููŠุด torch.compile (ู…ู…ู†ูˆุน ุนู„ู‰ ZeroGPU)
"""
import os
import sys
import glob
import tempfile
import subprocess
import gradio as gr
import spaces
# ---------- ุฑู‚ุนุฉ ู„ู€ bug ู…ุนุฑูˆู ููŠ gradio_client (additionalProperties=bool) ----------
# ุจูŠูƒุณุฑ ุจู†ุงุก ุงู„ู€ API schema ุจุฑุณุงู„ุฉ: argument of type 'bool' is not iterable
import gradio_client.utils as _gcu
_orig_json = _gcu._json_schema_to_python_type
_orig_get_type = _gcu.get_type
def _safe_get_type(schema):
if not isinstance(schema, dict):
return "Any"
return _orig_get_type(schema)
def _safe_json(schema, defs=None):
if isinstance(schema, bool):
return "Any"
return _orig_json(schema, defs)
_gcu.get_type = _safe_get_type
_gcu._json_schema_to_python_type = _safe_json
# ---------- ุชุญู…ูŠู„ ุงู„ู…ูˆุฏูŠู„ ู…ุฑุฉ ูˆุงุญุฏุฉ (read-only global) ----------
# ู†ุฎู„ู‘ูŠ ุญุฒู…ุฉ melband ุชู†ุฒู‘ู„ ุงู„ู…ูˆุฏูŠู„ + ุงู„ุฅุนุฏุงุฏุงุช ุจู†ูุณู‡ุง (ุจุตูŠุบุฉ ู…ุชูˆุงูู‚ุฉ ู…ุน safe_load)
import glob as _glob
MODEL_SLUG = "melband-roformer-kim-vocals"
MODELS_DIR = os.path.join(tempfile.gettempdir(), "safi_models")
os.makedirs(MODELS_DIR, exist_ok=True)
# ู†ู†ุฒู‘ู„ ุงู„ู…ูˆุฏูŠู„ ุจุฃู…ุฑ ุงู„ุญุฒู…ุฉ ุงู„ู…ุจุงุดุฑ (ู…ุซุจุช ุฅู†ู‡ ุนู„ู‰ ุงู„ู€ PATH ูˆู‚ุช ุงู„ุชุดุบูŠู„)
_model_dir = os.path.join(MODELS_DIR, MODEL_SLUG)
if not os.path.isdir(_model_dir) or not _glob.glob(os.path.join(_model_dir, "*.ckpt")):
subprocess.run(
["melband-roformer-download",
"--model", MODEL_SLUG, "--output-dir", MODELS_DIR],
check=True,
)
_cfgs = _glob.glob(os.path.join(_model_dir, "*.yaml"))
_ckpts = _glob.glob(os.path.join(_model_dir, "*.ckpt"))
if not _cfgs or not _ckpts:
raise RuntimeError(f"ุชุญู…ูŠู„ ุงู„ู…ูˆุฏูŠู„ ูุดู„ุŒ ู…ููŠุด ู…ู„ูุงุช ููŠ {_model_dir}")
CONFIG_PATH = _cfgs[0]
CKPT_PATH = _ckpts[0]
def _to_wav(src, dst):
"""ุชุญูˆูŠู„ ุฃูŠ ุตูŠุบุฉ ู„ู€ WAV ุณุชูŠุฑูŠูˆ 44.1kHz."""
subprocess.run(
["ffmpeg", "-y", "-i", src, "-vn", "-acodec", "pcm_s16le",
"-ar", "44100", "-ac", "2", dst],
check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL,
)
@spaces.GPU(duration=60)
def _separate_gpu(in_dir, out_dir):
"""ุงู„ุฌุฒุก ุงู„ู„ูŠ ู…ุญุชุงุฌ GPU: ุชุดุบูŠู„ ุงู„ู…ูˆุฏูŠู„ ุนู„ู‰ ุฌุฒุก ูˆุงุญุฏ. (ZeroGPU)"""
subprocess.run(
["melband-roformer-infer",
"--config_path", CONFIG_PATH,
"--model_path", CKPT_PATH,
"--input_folder", in_dir,
"--store_dir", out_dir],
check=True,
)
# ---------- ุงู„ุชู‚ุณูŠู… ู„ุฃุฌุฒุงุก + ุงู„ุชุฌู…ูŠุน ู…ุน cross-fade ----------
import numpy as _np
import soundfile as _sf
CHUNK_SEC = 20.0 # ุทูˆู„ ูƒู„ ุฌุฒุก (ุซูˆุงู†ูŠ) โ€” ูƒู„ ุฌุฒุก = ุงุณุชุฏุนุงุก GPU ู‚ุตูŠุฑ
OVERLAP_SEC = 1.0 # ุชุฏุงุฎู„ ุจูŠู† ุงู„ุฃุฌุฒุงุก (ุนุดุงู† ู…ููŠุด ู‚ุทุน ู…ุณู…ูˆุน)
def _separate_in_chunks(wav_path, work, progress=None):
"""
ุจูŠู‚ุณู‘ู… ุงู„ู…ู„ู ู„ุฃุฌุฒุงุกุŒ ูŠูุตู„ ูƒู„ ุฌุฒุก ููŠ ุงุณุชุฏุนุงุก GPU ู‚ุตูŠุฑุŒ
ูˆูŠุฌู…ู‘ุนู‡ู… ุจู†ุธุงู… cross-fade. ุจูŠุฑุฌู‘ุน (vocals_path, music_path).
"""
audio, sr = _sf.read(wav_path, always_2d=True) # (ุนูŠู†ุงุช, ู‚ู†ูˆุงุช)
total = audio.shape[0]
ch = audio.shape[1]
chunk = int(CHUNK_SEC * sr)
ov = int(OVERLAP_SEC * sr)
step = chunk - ov
# ุญุฏูˆุฏ ุงู„ุฃุฌุฒุงุก
spans = []
s = 0
while s < total:
e = min(s + chunk, total)
spans.append((s, e))
if e >= total:
break
s += step
voc = _np.zeros((total, ch), dtype=_np.float64)
mus = _np.zeros((total, ch), dtype=_np.float64)
wsum = _np.zeros((total, 1), dtype=_np.float64)
for i, (s, e) in enumerate(spans):
if progress is not None:
progress((i, len(spans)), desc=f"ุจูŠูุตู„ ุงู„ุฌุฒุก {i+1}/{len(spans)}")
cdir = os.path.join(work, f"c{i}")
cin = os.path.join(cdir, "in")
cout = os.path.join(cdir, "out")
os.makedirs(cin, exist_ok=True)
os.makedirs(cout, exist_ok=True)
_sf.write(os.path.join(cin, "track.wav"), audio[s:e], sr)
_separate_gpu(cin, cout) # ุงุณุชุฏุนุงุก GPU ู‚ุตูŠุฑ ู„ูƒู„ ุฌุฒุก
v = glob.glob(os.path.join(cout, "*vocals*.wav"))
m = glob.glob(os.path.join(cout, "*instrumental*.wav"))
if not v:
raise gr.Error(f"ุงู„ูุตู„ ูุดู„ ููŠ ุงู„ุฌุฒุก {i+1}")
vd, _ = _sf.read(v[0], always_2d=True)
md = _sf.read(m[0], always_2d=True)[0] if m else _np.zeros_like(vd)
n = e - s
vd = vd[:n]; md = md[:n]
w = _np.ones((n, 1))
if ov > 0:
ramp = _np.linspace(0, 1, min(ov, n)).reshape(-1, 1)
if s > 0:
w[:len(ramp)] *= ramp
if e < total:
w[-len(ramp):] *= ramp[::-1]
voc[s:e] += vd * w
mus[s:e] += md * w
wsum[s:e] += w
wsum[wsum < 1e-8] = 1.0
voc = (voc / wsum).astype(_np.float32)
mus = (mus / wsum).astype(_np.float32)
vpath = os.path.join(work, "vocals.wav")
mpath = os.path.join(work, "music.wav")
_sf.write(vpath, voc, sr)
_sf.write(mpath, mus, sr)
return vpath, mpath
def _download_youtube(url, out_path):
"""ูŠู†ุฒู‘ู„ ุฃูุถู„ ุตูˆุช ู…ู† ู„ูŠู†ูƒ ูŠูˆุชูŠูˆุจ ุจู€ yt-dlp ุฅู„ู‰ out_path (wav)."""
# ู…ู„ุงุญุธุฉ: ู„ู„ุชุฌุฑุจุฉ ุงู„ุดุฎุตูŠุฉ ูู‚ุท โ€” ุชู†ุฒูŠู„ ูŠูˆุชูŠูˆุจ ูŠุฎุงู„ู ุดุฑูˆุทู‡.
# ู†ู†ุงุฏูŠ yt-dlp ูƒู…ูˆุฏูŠูˆู„ ุจุงูŠุซูˆู† (ุฃุฃู…ู† ู…ู† ุงู„ุงุนุชู…ุงุฏ ุนู„ู‰ ูˆุฌูˆุฏู‡ ูƒุฃู…ุฑ ููŠ PATH)
base = out_path.rsplit(".", 1)[0]
subprocess.run(
[sys.executable, "-m", "yt_dlp",
"-x", "--audio-format", "wav", "--audio-quality", "0",
"-o", base + ".%(ext)s", url],
check=True,
)
def separate_link(url):
"""ุชุฌุฑูŠุจูŠ: ุจูŠุงุฎุฏ ู„ูŠู†ูƒ ูŠูˆุชูŠูˆุจุŒ ูŠู†ุฒู‘ู„ ุงู„ุตูˆุชุŒ ูŠูุตู„ู‡ุŒ ูˆูŠุฑุฌู‘ุน (ุงู„ูƒู„ุงู…, ุงู„ู…ูˆุณูŠู‚ู‰)."""
if not url or not url.strip():
return None, None
work = tempfile.mkdtemp()
dl_dir = os.path.join(work, "dl") # ุงู„ุชู†ุฒูŠู„ ู‡ู†ุง
os.makedirs(dl_dir, exist_ok=True)
raw = os.path.join(dl_dir, "yt.wav")
_download_youtube(url.strip(), raw)
got = glob.glob(os.path.join(dl_dir, "*.wav"))
if not got:
raise gr.Error("ู…ุด ู‚ุงุฏุฑ ุฃู†ุฒู‘ู„ ุงู„ุตูˆุช ู…ู† ุงู„ู„ูŠู†ูƒ ุฏู‡.")
wav = os.path.join(work, "track.wav")
_to_wav(got[0], wav)
return _separate_in_chunks(wav, work)
def separate(audio_file, progress=gr.Progress()):
"""ุจุชุงุฎุฏ ู…ู„ูุŒ ุชุญูˆู‘ู„ู‡ุŒ ุชูุตู„ู‡ ุฌุฒุก ุฌุฒุกุŒ ูˆุชุฑุฌู‘ุน (ุงู„ูƒู„ุงู…, ุงู„ู…ูˆุณูŠู‚ู‰)."""
if audio_file is None:
return None, None
work = tempfile.mkdtemp() # ูุฑูŠุฏ ู„ูƒู„ ุทู„ุจ
wav = os.path.join(work, "track.wav")
_to_wav(audio_file, wav) # CPU
return _separate_in_chunks(wav, work, progress) # GPU ุฌุฒุก ุฌุฒุก
# ---------- ูˆุงุฌู‡ุฉ Gradio (ุจู‡ูˆูŠุฉ ุตุงููŠ) ----------
CSS = """
.gradio-container{max-width:760px!important;margin:auto}
#safi-title{font-weight:900;font-size:30px;text-align:center;color:#3dd7b8}
#safi-sub{text-align:center;color:#8a97a3;margin-bottom:8px}
footer{visibility:hidden}
"""
with gr.Blocks(css=CSS, title="ุตุงููŠ โ€” ุฅุฒุงู„ุฉ ุงู„ู…ูˆุณูŠู‚ู‰", theme=gr.themes.Soft()) as demo:
gr.Markdown("# โ— ุตุงููŠ", elem_id="safi-title")
gr.Markdown("ุงุณู…ุน ุงู„ูƒู„ุงู…ุŒ ุณูŠุจูƒ ู…ู† ุงู„ู…ูˆุณูŠู‚ู‰ โ€” ูุตู„ ุจู…ูˆุฏูŠู„ Mel-Band RoFormer", elem_id="safi-sub")
with gr.Tab("ุงุฑูุน ู…ู„ู"):
inp = gr.Audio(label="ุงุฑูุน ู…ู„ู ุตูˆุช", type="filepath")
btn = gr.Button("ุงูุตู„ ุงู„ู…ูˆุณูŠู‚ู‰", variant="primary")
with gr.Tab("ู„ูŠู†ูƒ ูŠูˆุชูŠูˆุจ (ุชุฌุฑูŠุจูŠ)"):
gr.Markdown(
"โš ๏ธ ู„ู„ุชุฌุฑุจุฉ ุงู„ุดุฎุตูŠุฉ ูู‚ุท โ€” ุชู†ุฒูŠู„ ูŠูˆุชูŠูˆุจ ูŠุฎุงู„ู ุดุฑูˆุทู‡ ูˆู…ุด ู…ู†ุงุณุจ ู„ู„ู†ุดุฑ ุงู„ุนุงู….",
elem_id="safi-sub",
)
link = gr.Textbox(label="ู„ูŠู†ูƒ ูŠูˆุชูŠูˆุจ", placeholder="https://www.youtube.com/watch?v=...")
btn_link = gr.Button("ู†ุฒู‘ู„ ูˆุงูุตู„", variant="primary")
with gr.Row():
out_vocals = gr.Audio(label="ุงู„ูƒู„ุงู… (ุตุงููŠ)")
out_music = gr.Audio(label="ุงู„ู…ูˆุณูŠู‚ู‰")
btn.click(fn=separate, inputs=inp, outputs=[out_vocals, out_music], api_name="separate")
btn_link.click(fn=separate_link, inputs=link, outputs=[out_vocals, out_music], api_name="separate_link")
gr.Markdown(
"ุงู„ุชุฑุฎูŠุต MIT โ€” ุชุฌุงุฑูŠ. ุฃู†ุช ู…ุณุคูˆู„ ุนู† ุญู‚ูˆู‚ ุงู„ู…ุญุชูˆู‰ ุงู„ู„ูŠ ุชุฑูุนู‡.",
elem_id="safi-sub",
)
# api_name ุจูŠุฎู„ู‘ูŠ ุงู„ุฏุงู„ุฉ ู…ุชุงุญุฉ ูƒู€ API ู„ูˆุงุฌู‡ุฉ Cloudflare
demo.queue() # ู…ู‡ู… ู„ู€ ZeroGPU
if __name__ == "__main__":
# show_api=False ุจูŠุชุฌู†ู‘ุจ bug ุชูˆู„ูŠุฏ ุงู„ู€ schema ููŠ gradio_client
demo.launch()