ghostai1 commited on
Commit
e95bbe2
·
verified ·
1 Parent(s): 5f8e56d

Update public/apibinararybuild.py

Browse files
Files changed (1) hide show
  1. 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 # <-- added
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) — SAFE FORMAT + STYLE-DRIVEN UI CHANGES
313
  # ======================================================================================
314
 
315
  class SafeFormatDict(dict):
@@ -478,7 +477,7 @@ def load_model():
478
  musicgen_model = load_model()
479
 
480
  # ======================================================================================
481
- # GENERATION (30s CHUNKS, 60–120s READY)
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, "⚠️ Enter a prompt.", vram_status_text
589
 
590
  try:
591
  out_sr = int(output_sample_rate)
592
  except:
593
- return None, "Invalid sample rate.", vram_status_text
594
  try:
595
  bd = int(bit_depth)
596
  sample_width = 3 if bd == 24 else 2
597
  except:
598
- return None, "Invalid bit depth.", vram_status_text
599
  if not check_disk_space():
600
- return None, "⚠️ Low disk space (<1GB).", vram_status_text
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"Generate failed at chunk {chunk_idx}.", vram_status_text
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"Convert failed chunk {chunk_idx}.", vram_status_text
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"Post-process failed chunk {chunk_idx}.", vram_status_text
695
 
696
  if not segments:
697
- return None, "No audio generated.", vram_status_text
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"Export failed: {ee}", vram_status_text
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, "Generated.", vram_status_text
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 # NEW: pass style key for filename tagging
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
- # ASCII-safe header sanitizer
873
- # -----------------------------
874
- def _ascii_header(s: str) -> str:
875
- return re.sub(r'[^\x20-\x7E]', '', str(s or ''))
 
 
 
 
 
 
 
 
 
 
 
876
 
877
- # -----------------------------
878
- # BINARY MP3 RENDER ENDPOINT
879
- # -----------------------------
880
  @fastapp.post("/render")
881
  def render(req: RenderRequest):
882
  if is_busy():
883
- raise HTTPException(status_code=409, detail="Server busy")
 
 
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
- mp3_path, msg, vram = generate_music(
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
- if not mp3_path or not os.path.exists(mp3_path):
916
- raise HTTPException(status_code=500, detail=_ascii_header(msg or "No file produced"))
 
917
 
918
- filename = os.path.basename(mp3_path)
 
919
  headers = {
920
- "X-Job-ID": _ascii_header(job_id),
921
- "X-Status": _ascii_header(msg),
922
- "X-VRAM": _ascii_header(vram),
923
- "X-Release": _ascii_header(RELEASE),
924
  }
925
  return FileResponse(
926
- path=mp3_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"))) # NEW: style for filename
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 # update selected_style state for filename tagging
1121
  )
1122
 
1123
- for key, btn in row1 + row2 + row3 + row4:
1124
- if key == "foo_pad":
1125
- continue
1126
- btn.click(
1127
- set_prompt_and_settings_from_style,
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
- # Quick-sets
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 "Settings saved."
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
- "Settings loaded."
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
- "Defaults restored."
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(