techfreakworm commited on
Commit
7388985
·
unverified ·
1 Parent(s): b260242

feat(post): wire separate stems / normalise / mp3 export buttons in output panel (m5 g2)

Browse files
Files changed (3) hide show
  1. app.py +98 -0
  2. theme.py +40 -0
  3. ui.py +40 -1
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) bordered panels.
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"],