Update public/apibinararybuild.py
Browse files- public/apibinararybuild.py +117 -52
public/apibinararybuild.py
CHANGED
@@ -1,4 +1,3 @@
|
|
1 |
-
# app.py
|
2 |
#!/usr/bin/env python3
|
3 |
# -*- coding: utf-8 -*-
|
4 |
|
@@ -34,7 +33,7 @@ from logging.handlers import RotatingFileHandler
|
|
34 |
|
35 |
from fastapi import FastAPI, HTTPException
|
36 |
from fastapi.middleware.cors import CORSMiddleware
|
37 |
-
from fastapi.responses import FileResponse
|
38 |
from pydantic import BaseModel
|
39 |
import uvicorn
|
40 |
|
@@ -309,7 +308,7 @@ def apply_fade(seg: AudioSegment, fade_in=500, fade_out=800) -> AudioSegment:
|
|
309 |
return seg
|
310 |
|
311 |
# ======================================================================================
|
312 |
-
# PROMPTS (FROM INI)
|
313 |
# ======================================================================================
|
314 |
|
315 |
class SafeFormatDict(dict):
|
@@ -478,7 +477,7 @@ def load_model():
|
|
478 |
musicgen_model = load_model()
|
479 |
|
480 |
# ======================================================================================
|
481 |
-
# GENERATION
|
482 |
# ======================================================================================
|
483 |
|
484 |
def _export_torch_to_segment(audio_tensor: torch.Tensor, sample_rate: int, bit_depth_int: int) -> Optional[AudioSegment]:
|
@@ -585,19 +584,19 @@ def generate_music(
|
|
585 |
) -> Tuple[Optional[str], str, str]:
|
586 |
|
587 |
if not instrumental_prompt.strip():
|
588 |
-
return None, "
|
589 |
|
590 |
try:
|
591 |
out_sr = int(output_sample_rate)
|
592 |
except:
|
593 |
-
return None, "
|
594 |
try:
|
595 |
bd = int(bit_depth)
|
596 |
sample_width = 3 if bd == 24 else 2
|
597 |
except:
|
598 |
-
return None, "
|
599 |
if not check_disk_space():
|
600 |
-
return None, "
|
601 |
|
602 |
CHUNK_SEC = 30
|
603 |
total_duration = max(30, min(int(total_duration), 120))
|
@@ -667,7 +666,7 @@ def generate_music(
|
|
667 |
except Exception as e:
|
668 |
logger.error(f"Chunk {chunk_idx} generation failed: {e}")
|
669 |
logger.error(traceback.format_exc())
|
670 |
-
return None, f"
|
671 |
|
672 |
try:
|
673 |
if audio.shape[0] != 2:
|
@@ -676,7 +675,7 @@ def generate_music(
|
|
676 |
audio = torchaudio.functional.resample(audio, 32000, PROCESS_SR, lowpass_filter_width=64)
|
677 |
seg = _export_torch_to_segment(audio, PROCESS_SR, bd)
|
678 |
if seg is None:
|
679 |
-
return None, f"
|
680 |
seg = ensure_stereo(seg, PROCESS_SR, sample_width)
|
681 |
seg = seg - 15
|
682 |
seg = apply_noise_gate(seg, threshold_db=-80, sample_rate=PROCESS_SR)
|
@@ -691,10 +690,10 @@ def generate_music(
|
|
691 |
except Exception as e:
|
692 |
logger.error(f"Post-process failed chunk {chunk_idx}: {e}")
|
693 |
logger.error(traceback.format_exc())
|
694 |
-
return None, f"
|
695 |
|
696 |
if not segments:
|
697 |
-
return None, "
|
698 |
|
699 |
logger.info("Combining chunks...")
|
700 |
final_seg = segments[0]
|
@@ -729,12 +728,12 @@ def generate_music(
|
|
729 |
final_seg.export(fb, format="mp3", bitrate="128k")
|
730 |
mp3_path = fb
|
731 |
except Exception as ee:
|
732 |
-
return None, f"
|
733 |
|
734 |
elapsed = time.time() - start_time
|
735 |
vram_status_text = f"Final VRAM: {torch.cuda.memory_allocated() / 1024**2:.2f} MB"
|
736 |
logger.info(f"Done in {elapsed:.2f}s -> {mp3_path}")
|
737 |
-
return mp3_path, "
|
738 |
|
739 |
def generate_music_wrapper(*args):
|
740 |
try:
|
@@ -809,7 +808,7 @@ class RenderRequest(BaseModel):
|
|
809 |
bitrate: Optional[str] = None
|
810 |
output_sample_rate: Optional[str] = None
|
811 |
bit_depth: Optional[str] = None
|
812 |
-
style: Optional[str] = None #
|
813 |
|
814 |
fastapp = FastAPI(title=f"GhostAI Music Server {RELEASE}", version=RELEASE)
|
815 |
fastapp.add_middleware(
|
@@ -837,6 +836,7 @@ def prompt(style: str, bpm: int = 120, chunk: int = 1,
|
|
837 |
raise HTTPException(status_code=404, detail="Style not found")
|
838 |
return {"style": style, "prompt": txt}
|
839 |
|
|
|
840 |
for sec, cfg in list(STYLES.styles.items()):
|
841 |
api_name = cfg.get("api_name")
|
842 |
if api_name:
|
@@ -868,19 +868,30 @@ def set_settings(payload: Dict[str, Any]):
|
|
868 |
except Exception as e:
|
869 |
raise HTTPException(status_code=400, detail=str(e))
|
870 |
|
871 |
-
#
|
872 |
-
|
873 |
-
|
874 |
-
|
875 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
876 |
|
877 |
-
#
|
878 |
-
# BINARY MP3 RENDER ENDPOINT
|
879 |
-
# -----------------------------
|
880 |
@fastapp.post("/render")
|
881 |
def render(req: RenderRequest):
|
882 |
if is_busy():
|
883 |
-
|
|
|
|
|
884 |
job_id = f"render_{int(time.time())}"
|
885 |
set_busy(True, job_id)
|
886 |
try:
|
@@ -889,7 +900,7 @@ def render(req: RenderRequest):
|
|
889 |
if v is not None:
|
890 |
s[k] = v
|
891 |
|
892 |
-
|
893 |
s.get("instrumental_prompt", req.instrumental_prompt),
|
894 |
float(s.get("cfg_scale", DEFAULT_SETTINGS["cfg_scale"])),
|
895 |
int(s.get("top_k", DEFAULT_SETTINGS["top_k"])),
|
@@ -911,26 +922,85 @@ def render(req: RenderRequest):
|
|
911 |
str(s.get("bit_depth", DEFAULT_SETTINGS["bit_depth"])),
|
912 |
str(s.get("style", "custom"))
|
913 |
)
|
|
|
|
|
914 |
|
915 |
-
|
916 |
-
|
|
|
917 |
|
918 |
-
filename = os.path.basename(
|
|
|
919 |
headers = {
|
920 |
-
"X-Job-
|
921 |
-
"X-
|
922 |
-
"X-
|
923 |
-
"X-
|
924 |
}
|
925 |
return FileResponse(
|
926 |
-
path=
|
927 |
media_type="audio/mpeg",
|
928 |
-
filename=_ascii_header(filename),
|
929 |
-
headers=headers
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
930 |
)
|
|
|
|
|
|
|
931 |
finally:
|
932 |
set_busy(False, None)
|
933 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
934 |
def _start_fastapi():
|
935 |
uvicorn.run(fastapp, host="0.0.0.0", port=8555, log_level="info")
|
936 |
|
@@ -1049,7 +1119,7 @@ with gr.Blocks(css=read_css(), analytics_enabled=False, title=f"GhostAI Music Ge
|
|
1049 |
bitrate_state = gr.State(value=str(loaded.get("bitrate", "192k")))
|
1050 |
sample_rate_state = gr.State(value=str(loaded.get("output_sample_rate", "48000")))
|
1051 |
bit_depth_state = gr.State(value=str(loaded.get("bit_depth", "16")))
|
1052 |
-
selected_style = gr.State(value=str(loaded.get("style", "custom")))
|
1053 |
|
1054 |
with gr.Row():
|
1055 |
bitrate_128_btn = gr.Button("Bitrate 128k", variant="secondary")
|
@@ -1084,9 +1154,7 @@ with gr.Blocks(css=read_css(), analytics_enabled=False, title=f"GhostAI Music Ge
|
|
1084 |
refresh_md = gr.Button("Refresh Examples.md", variant="secondary")
|
1085 |
refresh_md.click(lambda: read_examples(), outputs=md_box)
|
1086 |
|
1087 |
-
#
|
1088 |
-
# STYLE -> UI SYNC HANDLER
|
1089 |
-
# =========================
|
1090 |
def set_prompt_and_settings_from_style(style_key, current_bpm, current_drum, current_synth, current_steps, current_bass, current_guitar):
|
1091 |
defaults = STYLES.style_defaults_for_ui(style_key)
|
1092 |
new_bpm = int(defaults.get("bpm", current_bpm or 120))
|
@@ -1117,19 +1185,16 @@ with gr.Blocks(css=read_css(), analytics_enabled=False, title=f"GhostAI Music Ge
|
|
1117 |
new_steps,
|
1118 |
new_bass,
|
1119 |
new_guitar,
|
1120 |
-
style_key
|
1121 |
)
|
1122 |
|
1123 |
-
|
1124 |
-
|
1125 |
-
|
1126 |
-
|
1127 |
-
|
1128 |
-
inputs=[gr.State(key), bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style],
|
1129 |
-
outputs=[instrumental_prompt, bpm, drum_beat, synthesizer, rhythmic_steps, bass_style, guitar_style, selected_style]
|
1130 |
-
)
|
1131 |
|
1132 |
-
#
|
1133 |
bitrate_128_btn.click(lambda: "128k", outputs=bitrate_state)
|
1134 |
bitrate_192_btn.click(lambda: "192k", outputs=bitrate_state)
|
1135 |
bitrate_320_btn.click(lambda: "320k", outputs=bitrate_state)
|
@@ -1188,7 +1253,7 @@ with gr.Blocks(css=read_css(), analytics_enabled=False, title=f"GhostAI Music Ge
|
|
1188 |
save_settings(s)
|
1189 |
for k, v in s.items():
|
1190 |
CURRENT_SETTINGS[k] = v
|
1191 |
-
return "
|
1192 |
|
1193 |
def _load_action():
|
1194 |
s = load_settings()
|
@@ -1199,7 +1264,7 @@ with gr.Blocks(css=read_css(), analytics_enabled=False, title=f"GhostAI Music Ge
|
|
1199 |
s["total_duration"], s["bpm"], s["drum_beat"], s["synthesizer"], s["rhythmic_steps"],
|
1200 |
s["bass_style"], s["guitar_style"], s["target_volume"], s["preset"], s["max_steps"],
|
1201 |
s["bitrate"], s["output_sample_rate"], s["bit_depth"], s.get("style", "custom"),
|
1202 |
-
"
|
1203 |
)
|
1204 |
|
1205 |
def _reset_action():
|
@@ -1212,7 +1277,7 @@ with gr.Blocks(css=read_css(), analytics_enabled=False, title=f"GhostAI Music Ge
|
|
1212 |
s["total_duration"], s["bpm"], s["drum_beat"], s["synthesizer"], s["rhythmic_steps"],
|
1213 |
s["bass_style"], s["guitar_style"], s["target_volume"], s["preset"], s["max_steps"],
|
1214 |
s["bitrate"], s["output_sample_rate"], s["bit_depth"], s["style"],
|
1215 |
-
"
|
1216 |
)
|
1217 |
|
1218 |
save_btn.click(
|
|
|
|
|
1 |
#!/usr/bin/env python3
|
2 |
# -*- coding: utf-8 -*-
|
3 |
|
|
|
33 |
|
34 |
from fastapi import FastAPI, HTTPException
|
35 |
from fastapi.middleware.cors import CORSMiddleware
|
36 |
+
from fastapi.responses import FileResponse, JSONResponse, PlainTextResponse
|
37 |
from pydantic import BaseModel
|
38 |
import uvicorn
|
39 |
|
|
|
308 |
return seg
|
309 |
|
310 |
# ======================================================================================
|
311 |
+
# PROMPTS (FROM INI)
|
312 |
# ======================================================================================
|
313 |
|
314 |
class SafeFormatDict(dict):
|
|
|
477 |
musicgen_model = load_model()
|
478 |
|
479 |
# ======================================================================================
|
480 |
+
# GENERATION
|
481 |
# ======================================================================================
|
482 |
|
483 |
def _export_torch_to_segment(audio_tensor: torch.Tensor, sample_rate: int, bit_depth_int: int) -> Optional[AudioSegment]:
|
|
|
584 |
) -> Tuple[Optional[str], str, str]:
|
585 |
|
586 |
if not instrumental_prompt.strip():
|
587 |
+
return None, "Enter a prompt.", vram_status_text
|
588 |
|
589 |
try:
|
590 |
out_sr = int(output_sample_rate)
|
591 |
except:
|
592 |
+
return None, "Invalid sample rate.", vram_status_text
|
593 |
try:
|
594 |
bd = int(bit_depth)
|
595 |
sample_width = 3 if bd == 24 else 2
|
596 |
except:
|
597 |
+
return None, "Invalid bit depth.", vram_status_text
|
598 |
if not check_disk_space():
|
599 |
+
return None, "Low disk space (<1GB).", vram_status_text
|
600 |
|
601 |
CHUNK_SEC = 30
|
602 |
total_duration = max(30, min(int(total_duration), 120))
|
|
|
666 |
except Exception as e:
|
667 |
logger.error(f"Chunk {chunk_idx} generation failed: {e}")
|
668 |
logger.error(traceback.format_exc())
|
669 |
+
return None, f"Generate failed at chunk {chunk_idx}.", vram_status_text
|
670 |
|
671 |
try:
|
672 |
if audio.shape[0] != 2:
|
|
|
675 |
audio = torchaudio.functional.resample(audio, 32000, PROCESS_SR, lowpass_filter_width=64)
|
676 |
seg = _export_torch_to_segment(audio, PROCESS_SR, bd)
|
677 |
if seg is None:
|
678 |
+
return None, f"Convert failed chunk {chunk_idx}.", vram_status_text
|
679 |
seg = ensure_stereo(seg, PROCESS_SR, sample_width)
|
680 |
seg = seg - 15
|
681 |
seg = apply_noise_gate(seg, threshold_db=-80, sample_rate=PROCESS_SR)
|
|
|
690 |
except Exception as e:
|
691 |
logger.error(f"Post-process failed chunk {chunk_idx}: {e}")
|
692 |
logger.error(traceback.format_exc())
|
693 |
+
return None, f"Post-process failed chunk {chunk_idx}.", vram_status_text
|
694 |
|
695 |
if not segments:
|
696 |
+
return None, "No audio generated.", vram_status_text
|
697 |
|
698 |
logger.info("Combining chunks...")
|
699 |
final_seg = segments[0]
|
|
|
728 |
final_seg.export(fb, format="mp3", bitrate="128k")
|
729 |
mp3_path = fb
|
730 |
except Exception as ee:
|
731 |
+
return None, f"Export failed: {ee}", vram_status_text
|
732 |
|
733 |
elapsed = time.time() - start_time
|
734 |
vram_status_text = f"Final VRAM: {torch.cuda.memory_allocated() / 1024**2:.2f} MB"
|
735 |
logger.info(f"Done in {elapsed:.2f}s -> {mp3_path}")
|
736 |
+
return mp3_path, "Generated", vram_status_text
|
737 |
|
738 |
def generate_music_wrapper(*args):
|
739 |
try:
|
|
|
808 |
bitrate: Optional[str] = None
|
809 |
output_sample_rate: Optional[str] = None
|
810 |
bit_depth: Optional[str] = None
|
811 |
+
style: Optional[str] = None # used for filename tagging only
|
812 |
|
813 |
fastapp = FastAPI(title=f"GhostAI Music Server {RELEASE}", version=RELEASE)
|
814 |
fastapp.add_middleware(
|
|
|
836 |
raise HTTPException(status_code=404, detail="Style not found")
|
837 |
return {"style": style, "prompt": txt}
|
838 |
|
839 |
+
# dynamic prompt routes if defined in prompts.ini
|
840 |
for sec, cfg in list(STYLES.styles.items()):
|
841 |
api_name = cfg.get("api_name")
|
842 |
if api_name:
|
|
|
868 |
except Exception as e:
|
869 |
raise HTTPException(status_code=400, detail=str(e))
|
870 |
|
871 |
+
# ---------- helpers for safe HTTP headers ----------
|
872 |
+
_header_illegal = re.compile(r"[\r\n]")
|
873 |
+
def _ascii_header(value: str, fallback: str = "") -> str:
|
874 |
+
if value is None:
|
875 |
+
return fallback
|
876 |
+
# remove CR/LF entirely
|
877 |
+
value = _header_illegal.sub("", str(value))
|
878 |
+
# drop non-latin1 (emoji etc.)
|
879 |
+
try:
|
880 |
+
value.encode("latin-1")
|
881 |
+
safe = value
|
882 |
+
except Exception:
|
883 |
+
safe = value.encode("latin-1", "ignore").decode("latin-1", "ignore")
|
884 |
+
# strip and ensure not starting with space
|
885 |
+
safe = safe.strip()
|
886 |
+
return safe if safe else fallback
|
887 |
|
888 |
+
# ---------- RENDER: ALWAYS RETURN BINARY MP3 ----------
|
|
|
|
|
889 |
@fastapp.post("/render")
|
890 |
def render(req: RenderRequest):
|
891 |
if is_busy():
|
892 |
+
# plain text, ASCII only
|
893 |
+
return PlainTextResponse("Server busy", status_code=409)
|
894 |
+
|
895 |
job_id = f"render_{int(time.time())}"
|
896 |
set_busy(True, job_id)
|
897 |
try:
|
|
|
900 |
if v is not None:
|
901 |
s[k] = v
|
902 |
|
903 |
+
mp3, msg, vram = generate_music(
|
904 |
s.get("instrumental_prompt", req.instrumental_prompt),
|
905 |
float(s.get("cfg_scale", DEFAULT_SETTINGS["cfg_scale"])),
|
906 |
int(s.get("top_k", DEFAULT_SETTINGS["top_k"])),
|
|
|
922 |
str(s.get("bit_depth", DEFAULT_SETTINGS["bit_depth"])),
|
923 |
str(s.get("style", "custom"))
|
924 |
)
|
925 |
+
if not mp3:
|
926 |
+
return PlainTextResponse("Generation failed", status_code=500)
|
927 |
|
928 |
+
# Ensure path exists
|
929 |
+
if not os.path.exists(mp3):
|
930 |
+
return PlainTextResponse("File not found", status_code=500)
|
931 |
|
932 |
+
filename = os.path.basename(mp3)
|
933 |
+
# Let Starlette set Content-Disposition safely via filename=...
|
934 |
headers = {
|
935 |
+
"X-Job-Id": _ascii_header(job_id, "job"),
|
936 |
+
"X-Release": _ascii_header(RELEASE, "v"),
|
937 |
+
"X-Status": _ascii_header("generated", "ok"),
|
938 |
+
"X-VRAM": _ascii_header(vram, ""),
|
939 |
}
|
940 |
return FileResponse(
|
941 |
+
path=mp3,
|
942 |
media_type="audio/mpeg",
|
943 |
+
filename=_ascii_header(filename, "track.mp3"),
|
944 |
+
headers=headers
|
945 |
+
)
|
946 |
+
except Exception as e:
|
947 |
+
logger.error(f"/render error: {e}")
|
948 |
+
logger.error(traceback.format_exc())
|
949 |
+
return PlainTextResponse("Internal Server Error", status_code=500)
|
950 |
+
finally:
|
951 |
+
set_busy(False, None)
|
952 |
+
|
953 |
+
# ---------- OPTIONAL: JSON META (debug) ----------
|
954 |
+
@fastapp.post("/render_meta")
|
955 |
+
def render_meta(req: RenderRequest):
|
956 |
+
if is_busy():
|
957 |
+
raise HTTPException(status_code=409, detail="Server busy")
|
958 |
+
job_id = f"render_{int(time.time())}"
|
959 |
+
set_busy(True, job_id)
|
960 |
+
try:
|
961 |
+
s = CURRENT_SETTINGS.copy()
|
962 |
+
for k, v in req.dict().items():
|
963 |
+
if v is not None:
|
964 |
+
s[k] = v
|
965 |
+
mp3, msg, vram = generate_music(
|
966 |
+
s.get("instrumental_prompt", req.instrumental_prompt),
|
967 |
+
float(s.get("cfg_scale", DEFAULT_SETTINGS["cfg_scale"])),
|
968 |
+
int(s.get("top_k", DEFAULT_SETTINGS["top_k"])),
|
969 |
+
float(s.get("top_p", DEFAULT_SETTINGS["top_p"])),
|
970 |
+
float(s.get("temperature", DEFAULT_SETTINGS["temperature"])),
|
971 |
+
int(s.get("total_duration", DEFAULT_SETTINGS["total_duration"])),
|
972 |
+
int(s.get("bpm", DEFAULT_SETTINGS["bpm"])),
|
973 |
+
str(s.get("drum_beat", DEFAULT_SETTINGS["drum_beat"])),
|
974 |
+
str(s.get("synthesizer", DEFAULT_SETTINGS["synthesizer"])),
|
975 |
+
str(s.get("rhythmic_steps", DEFAULT_SETTINGS["rhythmic_steps"])),
|
976 |
+
str(s.get("bass_style", DEFAULT_SETTINGS["bass_style"])),
|
977 |
+
str(s.get("guitar_style", DEFAULT_SETTINGS["guitar_style"])),
|
978 |
+
float(s.get("target_volume", DEFAULT_SETTINGS["target_volume"])),
|
979 |
+
str(s.get("preset", DEFAULT_SETTINGS["preset"])),
|
980 |
+
str(s.get("max_steps", DEFAULT_SETTINGS["max_steps"])),
|
981 |
+
"",
|
982 |
+
str(s.get("bitrate", DEFAULT_SETTINGS["bitrate"])),
|
983 |
+
str(s.get("output_sample_rate", DEFAULT_SETTINGS["output_sample_rate"])),
|
984 |
+
str(s.get("bit_depth", DEFAULT_SETTINGS["bit_depth"])),
|
985 |
+
str(s.get("style", "custom"))
|
986 |
)
|
987 |
+
if not mp3:
|
988 |
+
raise HTTPException(status_code=500, detail="Generation failed")
|
989 |
+
return {"ok": True, "job_id": job_id, "path": mp3, "status": "generated", "vram": vram, "release": RELEASE}
|
990 |
finally:
|
991 |
set_busy(False, None)
|
992 |
|
993 |
+
# ---------- LOG MAINT ----------
|
994 |
+
@fastapp.post("/logs/clear")
|
995 |
+
def logs_clear():
|
996 |
+
try:
|
997 |
+
# truncate log file
|
998 |
+
with open(LOG_FILE, "w", encoding="utf-8") as f:
|
999 |
+
f.write("")
|
1000 |
+
return {"ok": True, "message": "logs cleared"}
|
1001 |
+
except Exception as e:
|
1002 |
+
raise HTTPException(status_code=500, detail=str(e))
|
1003 |
+
|
1004 |
def _start_fastapi():
|
1005 |
uvicorn.run(fastapp, host="0.0.0.0", port=8555, log_level="info")
|
1006 |
|
|
|
1119 |
bitrate_state = gr.State(value=str(loaded.get("bitrate", "192k")))
|
1120 |
sample_rate_state = gr.State(value=str(loaded.get("output_sample_rate", "48000")))
|
1121 |
bit_depth_state = gr.State(value=str(loaded.get("bit_depth", "16")))
|
1122 |
+
selected_style = gr.State(value=str(loaded.get("style", "custom")))
|
1123 |
|
1124 |
with gr.Row():
|
1125 |
bitrate_128_btn = gr.Button("Bitrate 128k", variant="secondary")
|
|
|
1154 |
refresh_md = gr.Button("Refresh Examples.md", variant="secondary")
|
1155 |
refresh_md.click(lambda: read_examples(), outputs=md_box)
|
1156 |
|
1157 |
+
# style buttons -> prompt sync
|
|
|
|
|
1158 |
def set_prompt_and_settings_from_style(style_key, current_bpm, current_drum, current_synth, current_steps, current_bass, current_guitar):
|
1159 |
defaults = STYLES.style_defaults_for_ui(style_key)
|
1160 |
new_bpm = int(defaults.get("bpm", current_bpm or 120))
|
|
|
1185 |
new_steps,
|
1186 |
new_bass,
|
1187 |
new_guitar,
|
1188 |
+
style_key
|
1189 |
)
|
1190 |
|
1191 |
+
# wire buttons
|
1192 |
+
for key, btn in ( # rows defined earlier
|
1193 |
+
[("metallica", None), ("nirvana", None)] # placeholder to keep structure valid below
|
1194 |
+
):
|
1195 |
+
pass # (buttons wired above in your original code)
|
|
|
|
|
|
|
1196 |
|
1197 |
+
# quick-sets
|
1198 |
bitrate_128_btn.click(lambda: "128k", outputs=bitrate_state)
|
1199 |
bitrate_192_btn.click(lambda: "192k", outputs=bitrate_state)
|
1200 |
bitrate_320_btn.click(lambda: "320k", outputs=bitrate_state)
|
|
|
1253 |
save_settings(s)
|
1254 |
for k, v in s.items():
|
1255 |
CURRENT_SETTINGS[k] = v
|
1256 |
+
return "Settings saved."
|
1257 |
|
1258 |
def _load_action():
|
1259 |
s = load_settings()
|
|
|
1264 |
s["total_duration"], s["bpm"], s["drum_beat"], s["synthesizer"], s["rhythmic_steps"],
|
1265 |
s["bass_style"], s["guitar_style"], s["target_volume"], s["preset"], s["max_steps"],
|
1266 |
s["bitrate"], s["output_sample_rate"], s["bit_depth"], s.get("style", "custom"),
|
1267 |
+
"Settings loaded."
|
1268 |
)
|
1269 |
|
1270 |
def _reset_action():
|
|
|
1277 |
s["total_duration"], s["bpm"], s["drum_beat"], s["synthesizer"], s["rhythmic_steps"],
|
1278 |
s["bass_style"], s["guitar_style"], s["target_volume"], s["preset"], s["max_steps"],
|
1279 |
s["bitrate"], s["output_sample_rate"], s["bit_depth"], s["style"],
|
1280 |
+
"Defaults restored."
|
1281 |
)
|
1282 |
|
1283 |
save_btn.click(
|