Spaces:
Sleeping
Sleeping
urwebsiteaz-ux commited on
Commit ·
caf6590
1
Parent(s): abf19d6
Initial AUTOLYRICS space
Browse files- README.md +8 -6
- app.py +159 -0
- examples/ballad_clip.wav +0 -0
- examples/pop_clip.wav +0 -0
- examples/rap_clip.wav +0 -0
- requirements.txt +9 -0
README.md
CHANGED
|
@@ -1,13 +1,15 @@
|
|
| 1 |
---
|
| 2 |
-
title:
|
| 3 |
-
emoji:
|
| 4 |
colorFrom: gray
|
| 5 |
-
colorTo:
|
| 6 |
sdk: gradio
|
| 7 |
-
sdk_version: 6.
|
| 8 |
-
python_version: '3.13'
|
| 9 |
app_file: app.py
|
| 10 |
pinned: false
|
|
|
|
|
|
|
|
|
|
| 11 |
---
|
| 12 |
|
| 13 |
-
|
|
|
|
| 1 |
---
|
| 2 |
+
title: AUTOLYRICS
|
| 3 |
+
emoji: 🎙
|
| 4 |
colorFrom: gray
|
| 5 |
+
colorTo: black
|
| 6 |
sdk: gradio
|
| 7 |
+
sdk_version: 5.6.0
|
|
|
|
| 8 |
app_file: app.py
|
| 9 |
pinned: false
|
| 10 |
+
license: apache-2.0
|
| 11 |
+
suggested_hardware: t4-small
|
| 12 |
+
short_description: Singing-voice lyrics transcription via fine-tuned Whisper.
|
| 13 |
---
|
| 14 |
|
| 15 |
+
See [GitHub](https://github.com/ram.duvvuri/autolyrics) for the full story.
|
app.py
ADDED
|
@@ -0,0 +1,159 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""AUTOLYRICS — side-by-side baseline vs fine-tuned Gradio demo."""
|
| 2 |
+
import os
|
| 3 |
+
import time
|
| 4 |
+
import torch
|
| 5 |
+
import torchaudio
|
| 6 |
+
import gradio as gr
|
| 7 |
+
from transformers import WhisperProcessor, WhisperForConditionalGeneration
|
| 8 |
+
from peft import PeftModel
|
| 9 |
+
|
| 10 |
+
BASE_MODEL = "openai/whisper-small"
|
| 11 |
+
ADAPTER_REPO = os.environ.get(
|
| 12 |
+
"ADAPTER_REPO", "YOURNAME/autolyrics-whisper-small-lora")
|
| 13 |
+
|
| 14 |
+
DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
|
| 15 |
+
DTYPE = torch.float16 if DEVICE == "cuda" else torch.float32
|
| 16 |
+
|
| 17 |
+
# ---------- Lazy model loading ----------
|
| 18 |
+
print(f"Loading models on {DEVICE}…")
|
| 19 |
+
processor = WhisperProcessor.from_pretrained(BASE_MODEL)
|
| 20 |
+
|
| 21 |
+
baseline_model = WhisperForConditionalGeneration.from_pretrained(
|
| 22 |
+
BASE_MODEL, torch_dtype=DTYPE).to(DEVICE).eval()
|
| 23 |
+
for m in (baseline_model.config, baseline_model.generation_config):
|
| 24 |
+
m.language = "de"; m.task = "transcribe"
|
| 25 |
+
m.forced_decoder_ids = None; m.suppress_tokens = []
|
| 26 |
+
baseline_model.generation_config.no_repeat_ngram_size = 3
|
| 27 |
+
|
| 28 |
+
base_for_ft = WhisperForConditionalGeneration.from_pretrained(
|
| 29 |
+
BASE_MODEL, torch_dtype=DTYPE)
|
| 30 |
+
ft_model = PeftModel.from_pretrained(base_for_ft, ADAPTER_REPO).to(DEVICE).eval()
|
| 31 |
+
for m in (ft_model.config, ft_model.generation_config):
|
| 32 |
+
m.language = "de"; m.task = "transcribe"
|
| 33 |
+
m.forced_decoder_ids = None; m.suppress_tokens = []
|
| 34 |
+
ft_model.generation_config.no_repeat_ngram_size = 3
|
| 35 |
+
print("Models ready.")
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def load_audio(path: str) -> torch.Tensor:
|
| 39 |
+
wav, sr = torchaudio.load(path)
|
| 40 |
+
if wav.shape[0] > 1:
|
| 41 |
+
wav = wav.mean(0, keepdim=True)
|
| 42 |
+
if sr != 16000:
|
| 43 |
+
wav = torchaudio.functional.resample(wav, sr, 16000)
|
| 44 |
+
return wav.squeeze(0)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
@torch.inference_mode()
|
| 48 |
+
def transcribe_with(model, audio_tensor, num_beams: int):
|
| 49 |
+
feats = processor(audio_tensor.numpy(), sampling_rate=16000,
|
| 50 |
+
return_tensors="pt").input_features.to(DEVICE, dtype=DTYPE)
|
| 51 |
+
t0 = time.perf_counter()
|
| 52 |
+
ids = model.generate(feats, num_beams=num_beams, max_new_tokens=225,
|
| 53 |
+
return_dict_in_generate=True, output_scores=True)
|
| 54 |
+
dt = time.perf_counter() - t0
|
| 55 |
+
text = processor.batch_decode(ids.sequences, skip_special_tokens=True)[0].strip()
|
| 56 |
+
# crude confidence: mean negative log-likelihood normalized
|
| 57 |
+
if hasattr(ids, "sequences_scores") and ids.sequences_scores is not None:
|
| 58 |
+
conf = float(torch.exp(ids.sequences_scores[0]).clamp(0, 1))
|
| 59 |
+
else:
|
| 60 |
+
conf = None
|
| 61 |
+
return text, dt, conf
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def run(audio_path: str, num_beams: int, model_choice: str):
|
| 65 |
+
if audio_path is None:
|
| 66 |
+
return "—", "—", "—", "—", "Please upload audio."
|
| 67 |
+
audio = load_audio(audio_path)
|
| 68 |
+
duration = audio.shape[-1] / 16000
|
| 69 |
+
|
| 70 |
+
if model_choice == "Baseline only":
|
| 71 |
+
b_text, b_dt, b_conf = transcribe_with(baseline_model, audio, num_beams)
|
| 72 |
+
return b_text, "—", f"{b_dt:.2f}s · RTF {b_dt/duration:.2f}", "—", \
|
| 73 |
+
f"Audio: {duration:.1f}s"
|
| 74 |
+
if model_choice == "Fine-tuned only":
|
| 75 |
+
f_text, f_dt, f_conf = transcribe_with(ft_model, audio, num_beams)
|
| 76 |
+
return "—", f_text, "—", f"{f_dt:.2f}s · RTF {f_dt/duration:.2f}", \
|
| 77 |
+
f"Audio: {duration:.1f}s"
|
| 78 |
+
# both
|
| 79 |
+
b_text, b_dt, _ = transcribe_with(baseline_model, audio, num_beams)
|
| 80 |
+
f_text, f_dt, _ = transcribe_with(ft_model, audio, num_beams)
|
| 81 |
+
return b_text, f_text, \
|
| 82 |
+
f"{b_dt:.2f}s · RTF {b_dt/duration:.2f}", \
|
| 83 |
+
f"{f_dt:.2f}s · RTF {f_dt/duration:.2f}", \
|
| 84 |
+
f"Audio: {duration:.1f}s"
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# ---------- UI ----------
|
| 88 |
+
THEME = gr.themes.Monochrome(
|
| 89 |
+
primary_hue="neutral", neutral_hue="slate",
|
| 90 |
+
radius_size=gr.themes.sizes.radius_lg,
|
| 91 |
+
font=[gr.themes.GoogleFont("Inter"), "system-ui", "sans-serif"],
|
| 92 |
+
).set(
|
| 93 |
+
body_background_fill="#000000",
|
| 94 |
+
body_text_color="#fafafa",
|
| 95 |
+
block_background_fill="#0a0a0a",
|
| 96 |
+
block_border_color="#1a1a1a",
|
| 97 |
+
button_primary_background_fill="#fafafa",
|
| 98 |
+
button_primary_text_color="#000000",
|
| 99 |
+
)
|
| 100 |
+
|
| 101 |
+
CSS = """
|
| 102 |
+
#title { letter-spacing: -0.02em; }
|
| 103 |
+
.gradio-container { max-width: 1100px !important; }
|
| 104 |
+
footer { display: none !important; }
|
| 105 |
+
"""
|
| 106 |
+
|
| 107 |
+
with gr.Blocks(theme=THEME, css=CSS, title="AUTOLYRICS") as demo:
|
| 108 |
+
gr.HTML("""
|
| 109 |
+
<div style='padding: 28px 0 8px 0;'>
|
| 110 |
+
<h1 id='title' style='font-size: 44px; font-weight: 600; margin: 0;'>
|
| 111 |
+
AUTOLYRICS
|
| 112 |
+
</h1>
|
| 113 |
+
<p style='color: #888; margin: 8px 0 0 0; font-size: 15px;'>
|
| 114 |
+
Transcribing the voice inside music. Whisper-small fine-tuned with LoRA on singing.
|
| 115 |
+
</p>
|
| 116 |
+
</div>
|
| 117 |
+
""")
|
| 118 |
+
|
| 119 |
+
with gr.Row():
|
| 120 |
+
with gr.Column(scale=1):
|
| 121 |
+
audio = gr.Audio(type="filepath", label="Upload or record",
|
| 122 |
+
sources=["upload", "microphone"])
|
| 123 |
+
with gr.Row():
|
| 124 |
+
beams = gr.Slider(1, 8, value=5, step=1, label="Beam search width")
|
| 125 |
+
choice = gr.Radio(
|
| 126 |
+
["Both (compare)", "Baseline only", "Fine-tuned only"],
|
| 127 |
+
value="Both (compare)", label="Mode")
|
| 128 |
+
run_btn = gr.Button("Transcribe", variant="primary")
|
| 129 |
+
meta = gr.Markdown("")
|
| 130 |
+
|
| 131 |
+
with gr.Column(scale=1):
|
| 132 |
+
with gr.Group():
|
| 133 |
+
gr.Markdown("### Baseline · Whisper-small")
|
| 134 |
+
base_out = gr.Textbox(lines=4, show_label=False,
|
| 135 |
+
placeholder="Baseline transcription will appear here…")
|
| 136 |
+
base_meta = gr.Markdown("")
|
| 137 |
+
with gr.Group():
|
| 138 |
+
gr.Markdown("### Fine-tuned · AUTOLYRICS (LoRA)")
|
| 139 |
+
ft_out = gr.Textbox(lines=4, show_label=False,
|
| 140 |
+
placeholder="Fine-tuned transcription will appear here…")
|
| 141 |
+
ft_meta = gr.Markdown("")
|
| 142 |
+
|
| 143 |
+
gr.Examples(
|
| 144 |
+
examples=[
|
| 145 |
+
["examples/pop_clip.wav", 5, "Both (compare)"],
|
| 146 |
+
["examples/ballad_clip.wav",5, "Both (compare)"],
|
| 147 |
+
["examples/rap_clip.wav", 5, "Both (compare)"],
|
| 148 |
+
],
|
| 149 |
+
inputs=[audio, beams, choice],
|
| 150 |
+
)
|
| 151 |
+
|
| 152 |
+
run_btn.click(
|
| 153 |
+
run,
|
| 154 |
+
inputs=[audio, beams, choice],
|
| 155 |
+
outputs=[base_out, ft_out, base_meta, ft_meta, meta],
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
if __name__ == "__main__":
|
| 159 |
+
demo.queue(max_size=12).launch()
|
examples/ballad_clip.wav
ADDED
|
File without changes
|
examples/pop_clip.wav
ADDED
|
File without changes
|
examples/rap_clip.wav
ADDED
|
File without changes
|
requirements.txt
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
torch
|
| 2 |
+
torchaudio
|
| 3 |
+
transformers
|
| 4 |
+
peft
|
| 5 |
+
gradio
|
| 6 |
+
huggingface_hub
|
| 7 |
+
soundfile
|
| 8 |
+
accelerate
|
| 9 |
+
sentencepiece
|