Spaces:
Running on Zero
Running on Zero
feat(post): wire separate stems / normalise / mp3 export buttons in output panel (m5 g2)
Browse files
app.py
CHANGED
|
@@ -53,6 +53,7 @@ import ace_pipeline
|
|
| 53 |
import backend as be
|
| 54 |
import lora_stack
|
| 55 |
import modes
|
|
|
|
| 56 |
import theme
|
| 57 |
import ui
|
| 58 |
|
|
@@ -305,6 +306,39 @@ def on_draft_lyrics(
|
|
| 305 |
raise gr.Error(str(e)) from e
|
| 306 |
|
| 307 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 308 |
def on_edit_click(
|
| 309 |
source_audio,
|
| 310 |
sub_mode: str,
|
|
@@ -458,6 +492,22 @@ def build_app() -> gr.Blocks:
|
|
| 458 |
],
|
| 459 |
outputs=[g["output_audio"], g["output_meta"]],
|
| 460 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_cover:
|
| 462 |
c = ui.build_cover_tab()
|
| 463 |
c["lora_preset"].change(
|
|
@@ -487,6 +537,22 @@ def build_app() -> gr.Blocks:
|
|
| 487 |
],
|
| 488 |
outputs=[c["output_audio"], c["output_meta"]],
|
| 489 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_extend:
|
| 491 |
x = ui.build_extend_tab()
|
| 492 |
x["lora_preset"].change(
|
|
@@ -520,6 +586,22 @@ def build_app() -> gr.Blocks:
|
|
| 520 |
],
|
| 521 |
outputs=[x["output_audio"], x["output_meta"]],
|
| 522 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 523 |
with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_edit:
|
| 524 |
e = ui.build_edit_tab()
|
| 525 |
e["lora_preset"].change(
|
|
@@ -556,6 +638,22 @@ def build_app() -> gr.Blocks:
|
|
| 556 |
],
|
| 557 |
outputs=[e["output_audio"], e["output_meta"]],
|
| 558 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 559 |
with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_lyrics:
|
| 560 |
lyr = ui.build_lyrics_tab()
|
| 561 |
lyr["draft_btn"].click(
|
|
|
|
| 53 |
import backend as be
|
| 54 |
import lora_stack
|
| 55 |
import modes
|
| 56 |
+
import post_process
|
| 57 |
import theme
|
| 58 |
import ui
|
| 59 |
|
|
|
|
| 306 |
raise gr.Error(str(e)) from e
|
| 307 |
|
| 308 |
|
| 309 |
+
def on_separate_stems(audio_path):
|
| 310 |
+
"""Run Demucs on the current Output audio and surface 4 stem files."""
|
| 311 |
+
if not audio_path:
|
| 312 |
+
raise gr.Error("Generate a song first.")
|
| 313 |
+
try:
|
| 314 |
+
stems = post_process.separate_stems(audio_path)
|
| 315 |
+
except Exception as e:
|
| 316 |
+
raise gr.Error(f"Demucs failed: {e}") from e
|
| 317 |
+
return gr.Files(value=list(stems.values()), visible=True)
|
| 318 |
+
|
| 319 |
+
|
| 320 |
+
def on_normalise(audio_path):
|
| 321 |
+
"""Run pyloudnorm at -14 LUFS and surface the normalised WAV."""
|
| 322 |
+
if not audio_path:
|
| 323 |
+
raise gr.Error("Generate a song first.")
|
| 324 |
+
try:
|
| 325 |
+
out = post_process.normalise_lufs(audio_path, target_lufs=-14.0)
|
| 326 |
+
except Exception as e:
|
| 327 |
+
raise gr.Error(f"Normalisation failed: {e}") from e
|
| 328 |
+
return gr.Audio(value=str(out), visible=True)
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
def on_export_mp3(audio_path):
|
| 332 |
+
"""Encode the current Output to MP3 320 k via ffmpeg and surface the file."""
|
| 333 |
+
if not audio_path:
|
| 334 |
+
raise gr.Error("Generate a song first.")
|
| 335 |
+
try:
|
| 336 |
+
out = post_process.to_mp3(audio_path, bitrate_kbps=320)
|
| 337 |
+
except Exception as e:
|
| 338 |
+
raise gr.Error(f"MP3 export failed: {e}") from e
|
| 339 |
+
return gr.File(value=str(out), visible=True)
|
| 340 |
+
|
| 341 |
+
|
| 342 |
def on_edit_click(
|
| 343 |
source_audio,
|
| 344 |
sub_mode: str,
|
|
|
|
| 492 |
],
|
| 493 |
outputs=[g["output_audio"], g["output_meta"]],
|
| 494 |
)
|
| 495 |
+
# Post-processing actions (M5/G2)
|
| 496 |
+
g["separate_stems_btn"].click(
|
| 497 |
+
fn=on_separate_stems,
|
| 498 |
+
inputs=[g["output_audio"]],
|
| 499 |
+
outputs=[g["stem_files"]],
|
| 500 |
+
)
|
| 501 |
+
g["normalise_btn"].click(
|
| 502 |
+
fn=on_normalise,
|
| 503 |
+
inputs=[g["output_audio"]],
|
| 504 |
+
outputs=[g["normalised_audio"]],
|
| 505 |
+
)
|
| 506 |
+
g["mp3_btn"].click(
|
| 507 |
+
fn=on_export_mp3,
|
| 508 |
+
inputs=[g["output_audio"]],
|
| 509 |
+
outputs=[g["mp3_file"]],
|
| 510 |
+
)
|
| 511 |
with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_cover:
|
| 512 |
c = ui.build_cover_tab()
|
| 513 |
c["lora_preset"].change(
|
|
|
|
| 537 |
],
|
| 538 |
outputs=[c["output_audio"], c["output_meta"]],
|
| 539 |
)
|
| 540 |
+
# Post-processing actions (M5/G2)
|
| 541 |
+
c["separate_stems_btn"].click(
|
| 542 |
+
fn=on_separate_stems,
|
| 543 |
+
inputs=[c["output_audio"]],
|
| 544 |
+
outputs=[c["stem_files"]],
|
| 545 |
+
)
|
| 546 |
+
c["normalise_btn"].click(
|
| 547 |
+
fn=on_normalise,
|
| 548 |
+
inputs=[c["output_audio"]],
|
| 549 |
+
outputs=[c["normalised_audio"]],
|
| 550 |
+
)
|
| 551 |
+
c["mp3_btn"].click(
|
| 552 |
+
fn=on_export_mp3,
|
| 553 |
+
inputs=[c["output_audio"]],
|
| 554 |
+
outputs=[c["mp3_file"]],
|
| 555 |
+
)
|
| 556 |
with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_extend:
|
| 557 |
x = ui.build_extend_tab()
|
| 558 |
x["lora_preset"].change(
|
|
|
|
| 586 |
],
|
| 587 |
outputs=[x["output_audio"], x["output_meta"]],
|
| 588 |
)
|
| 589 |
+
# Post-processing actions (M5/G2)
|
| 590 |
+
x["separate_stems_btn"].click(
|
| 591 |
+
fn=on_separate_stems,
|
| 592 |
+
inputs=[x["output_audio"]],
|
| 593 |
+
outputs=[x["stem_files"]],
|
| 594 |
+
)
|
| 595 |
+
x["normalise_btn"].click(
|
| 596 |
+
fn=on_normalise,
|
| 597 |
+
inputs=[x["output_audio"]],
|
| 598 |
+
outputs=[x["normalised_audio"]],
|
| 599 |
+
)
|
| 600 |
+
x["mp3_btn"].click(
|
| 601 |
+
fn=on_export_mp3,
|
| 602 |
+
inputs=[x["output_audio"]],
|
| 603 |
+
outputs=[x["mp3_file"]],
|
| 604 |
+
)
|
| 605 |
with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_edit:
|
| 606 |
e = ui.build_edit_tab()
|
| 607 |
e["lora_preset"].change(
|
|
|
|
| 638 |
],
|
| 639 |
outputs=[e["output_audio"], e["output_meta"]],
|
| 640 |
)
|
| 641 |
+
# Post-processing actions (M5/G2)
|
| 642 |
+
e["separate_stems_btn"].click(
|
| 643 |
+
fn=on_separate_stems,
|
| 644 |
+
inputs=[e["output_audio"]],
|
| 645 |
+
outputs=[e["stem_files"]],
|
| 646 |
+
)
|
| 647 |
+
e["normalise_btn"].click(
|
| 648 |
+
fn=on_normalise,
|
| 649 |
+
inputs=[e["output_audio"]],
|
| 650 |
+
outputs=[e["normalised_audio"]],
|
| 651 |
+
)
|
| 652 |
+
e["mp3_btn"].click(
|
| 653 |
+
fn=on_export_mp3,
|
| 654 |
+
inputs=[e["output_audio"]],
|
| 655 |
+
outputs=[e["mp3_file"]],
|
| 656 |
+
)
|
| 657 |
with gr.Group(visible=False, elem_classes=["ams-tab-pane"]) as pane_lyrics:
|
| 658 |
lyr = ui.build_lyrics_tab()
|
| 659 |
lyr["draft_btn"].click(
|
theme.py
CHANGED
|
@@ -914,6 +914,46 @@ main, .contain {{
|
|
| 914 |
padding:0 12px 12px 12px !important;
|
| 915 |
}}
|
| 916 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 917 |
/* Hide Gradio footer + the floating "Use via API" / settings panel */
|
| 918 |
footer {{ display:none !important; }}
|
| 919 |
.show-api {{ display:none !important; }}
|
|
|
|
| 914 |
padding:0 12px 12px 12px !important;
|
| 915 |
}}
|
| 916 |
|
| 917 |
+
/* ============================================================
|
| 918 |
+
* Post-process action row (M5/G2) — sits below the Output Audio.
|
| 919 |
+
* Three compact mono pills (separate stems / normalise / mp3 export)
|
| 920 |
+
* that surface hidden gr.Files / gr.Audio / gr.File widgets once a
|
| 921 |
+
* post-process click handler returns. The bordered list chrome on
|
| 922 |
+
* stem_files + mp3_file matches the generic .ams-out treatment so
|
| 923 |
+
* the populated state reads as a continuation of the Output panel.
|
| 924 |
+
* ============================================================ */
|
| 925 |
+
.ams-content .ams-post-actions {{
|
| 926 |
+
gap: 6px !important;
|
| 927 |
+
margin: 8px 0 0 0 !important;
|
| 928 |
+
}}
|
| 929 |
+
.ams-content .ams-post-btn {{
|
| 930 |
+
font-family: {FONT_MONO} !important;
|
| 931 |
+
font-size: 11px !important;
|
| 932 |
+
letter-spacing: 0.04em !important;
|
| 933 |
+
padding: 8px 10px !important;
|
| 934 |
+
background: #000 !important;
|
| 935 |
+
border: 1px solid {BORDER} !important;
|
| 936 |
+
color: {INK} !important;
|
| 937 |
+
border-radius: 3px !important;
|
| 938 |
+
}}
|
| 939 |
+
.ams-content .ams-post-btn:hover {{
|
| 940 |
+
border-color: {PRIMARY} !important;
|
| 941 |
+
}}
|
| 942 |
+
/* Stem files + mp3 file widgets — compact bordered list */
|
| 943 |
+
.ams-content .ams-stem-files,
|
| 944 |
+
.ams-content .ams-mp3-file {{
|
| 945 |
+
background: #000 !important;
|
| 946 |
+
border: 1px solid {BORDER} !important;
|
| 947 |
+
border-radius: 3px !important;
|
| 948 |
+
margin-top: 6px !important;
|
| 949 |
+
}}
|
| 950 |
+
@media (max-width: 640px) {{
|
| 951 |
+
.ams-content .ams-post-btn {{
|
| 952 |
+
font-size: 10px !important;
|
| 953 |
+
padding: 7px 8px !important;
|
| 954 |
+
}}
|
| 955 |
+
}}
|
| 956 |
+
|
| 957 |
/* Hide Gradio footer + the floating "Use via API" / settings panel */
|
| 958 |
footer {{ display:none !important; }}
|
| 959 |
.show-api {{ display:none !important; }}
|
ui.py
CHANGED
|
@@ -73,7 +73,7 @@ def _build_lora_accordion(components: dict[str, gr.components.Component]) -> Non
|
|
| 73 |
|
| 74 |
|
| 75 |
def _build_output_panel(components: dict[str, gr.components.Component]) -> None:
|
| 76 |
-
"""Shared OUTPUT (gr.Audio) + METADATA (gr.JSON)
|
| 77 |
|
| 78 |
elem_classes on each output component give CSS hooks for the
|
| 79 |
Brutalist Mono treatment (uppercase mono labels + bordered
|
|
@@ -83,6 +83,12 @@ def _build_output_panel(components: dict[str, gr.components.Component]) -> None:
|
|
| 83 |
gr.JSON renders a dict directly as a syntax-highlighted, expandable
|
| 84 |
tree. gr.Code(language="json") refuses dicts — it requires a
|
| 85 |
pre-stringified blob — and crashes with "'dict' has no .strip()".
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 86 |
"""
|
| 87 |
components["output_audio"] = gr.Audio(
|
| 88 |
label="Output",
|
|
@@ -90,6 +96,39 @@ def _build_output_panel(components: dict[str, gr.components.Component]) -> None:
|
|
| 90 |
interactive=False,
|
| 91 |
elem_classes=["ams-out", "ams-out-audio"],
|
| 92 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 93 |
components["output_meta"] = gr.JSON(
|
| 94 |
label="Metadata",
|
| 95 |
elem_classes=["ams-out", "ams-out-meta"],
|
|
|
|
| 73 |
|
| 74 |
|
| 75 |
def _build_output_panel(components: dict[str, gr.components.Component]) -> None:
|
| 76 |
+
"""Shared OUTPUT (gr.Audio) + post-process actions + METADATA (gr.JSON).
|
| 77 |
|
| 78 |
elem_classes on each output component give CSS hooks for the
|
| 79 |
Brutalist Mono treatment (uppercase mono labels + bordered
|
|
|
|
| 83 |
gr.JSON renders a dict directly as a syntax-highlighted, expandable
|
| 84 |
tree. gr.Code(language="json") refuses dicts — it requires a
|
| 85 |
pre-stringified blob — and crashes with "'dict' has no .strip()".
|
| 86 |
+
|
| 87 |
+
Below the Audio we expose three secondary post-process actions
|
| 88 |
+
(M5/G2): Demucs stem separation, pyloudnorm LUFS normalisation, and
|
| 89 |
+
ffmpeg MP3 export. Each emits to a hidden output (stem_files /
|
| 90 |
+
normalised_audio / mp3_file) that becomes visible only once the
|
| 91 |
+
click handler returns a populated value.
|
| 92 |
"""
|
| 93 |
components["output_audio"] = gr.Audio(
|
| 94 |
label="Output",
|
|
|
|
| 96 |
interactive=False,
|
| 97 |
elem_classes=["ams-out", "ams-out-audio"],
|
| 98 |
)
|
| 99 |
+
with gr.Row(elem_classes=["ams-post-actions"]):
|
| 100 |
+
components["separate_stems_btn"] = gr.Button(
|
| 101 |
+
"↯ Separate stems",
|
| 102 |
+
variant="secondary",
|
| 103 |
+
elem_classes=["ams-post-btn"],
|
| 104 |
+
)
|
| 105 |
+
components["normalise_btn"] = gr.Button(
|
| 106 |
+
"▮ Normalise -14 LUFS",
|
| 107 |
+
variant="secondary",
|
| 108 |
+
elem_classes=["ams-post-btn"],
|
| 109 |
+
)
|
| 110 |
+
components["mp3_btn"] = gr.Button(
|
| 111 |
+
"↓ MP3 320k",
|
| 112 |
+
variant="secondary",
|
| 113 |
+
elem_classes=["ams-post-btn"],
|
| 114 |
+
)
|
| 115 |
+
components["stem_files"] = gr.Files(
|
| 116 |
+
label="Stems",
|
| 117 |
+
visible=False,
|
| 118 |
+
elem_classes=["ams-stem-files"],
|
| 119 |
+
)
|
| 120 |
+
components["normalised_audio"] = gr.Audio(
|
| 121 |
+
label="Normalised (-14 LUFS)",
|
| 122 |
+
type="filepath",
|
| 123 |
+
interactive=False,
|
| 124 |
+
visible=False,
|
| 125 |
+
elem_classes=["ams-out", "ams-out-normalised"],
|
| 126 |
+
)
|
| 127 |
+
components["mp3_file"] = gr.File(
|
| 128 |
+
label="MP3 download",
|
| 129 |
+
visible=False,
|
| 130 |
+
elem_classes=["ams-mp3-file"],
|
| 131 |
+
)
|
| 132 |
components["output_meta"] = gr.JSON(
|
| 133 |
label="Metadata",
|
| 134 |
elem_classes=["ams-out", "ams-out-meta"],
|