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

Update public/apibinararybuild.py

Browse files
Files changed (1) hide show
  1. public/apibinararybuild.py +30 -82
public/apibinararybuild.py CHANGED
@@ -1,13 +1,10 @@
 
1
  #!/usr/bin/env python3
2
  # -*- coding: utf-8 -*-
3
 
4
  # GhostAI Music Generator — Release v1.3.3
5
  # Gradio UI + FastAPI server, externalized styles (CSS), prompts (INI), and examples (MD).
6
  # Saves MP3s to ./mp3, single rotating log (max 5MB) in ./logs, colorized console.
7
- # CHANGES:
8
- # - /render returns MP3 binary (FileResponse) instead of JSON
9
- # - per-render metadata saved to ./json/<basename>.json
10
- # - hourly cleanup: delete files older than 48h from ./mp3, ./json, ./logs
11
 
12
  import os
13
  import sys
@@ -37,12 +34,11 @@ from logging.handlers import RotatingFileHandler
37
 
38
  from fastapi import FastAPI, HTTPException
39
  from fastapi.middleware.cors import CORSMiddleware
40
- from fastapi.responses import FileResponse
41
  from pydantic import BaseModel
42
  import uvicorn
43
 
44
  from colorama import init as colorama_init, Fore
45
- from datetime import datetime, timedelta
46
 
47
  RELEASE = "v1.3.3"
48
 
@@ -64,13 +60,10 @@ torch.backends.cudnn.benchmark = False
64
  torch.backends.cudnn.deterministic = True
65
 
66
  BASE_DIR = Path(__file__).parent.resolve()
67
- LOG_DIR = BASE_DIR / "logs"
68
- MP3_DIR = BASE_DIR / "mp3"
69
- JSON_DIR = BASE_DIR / "json" # NEW: metadata folder
70
-
71
  LOG_DIR.mkdir(parents=True, exist_ok=True)
72
  MP3_DIR.mkdir(parents=True, exist_ok=True)
73
- JSON_DIR.mkdir(parents=True, exist_ok=True)
74
 
75
  LOG_FILE = LOG_DIR / "ghostai_musicgen.log"
76
  logger = logging.getLogger("ghostai-musicgen")
@@ -816,7 +809,7 @@ class RenderRequest(BaseModel):
816
  bitrate: Optional[str] = None
817
  output_sample_rate: Optional[str] = None
818
  bit_depth: Optional[str] = None
819
- style: Optional[str] = None # passed for filename tagging
820
 
821
  fastapp = FastAPI(title=f"GhostAI Music Server {RELEASE}", version=RELEASE)
822
  fastapp.add_middleware(
@@ -875,25 +868,15 @@ def set_settings(payload: Dict[str, Any]):
875
  except Exception as e:
876
  raise HTTPException(status_code=400, detail=str(e))
877
 
878
- def _write_metadata(mp3_path: Path, job_id: str, status_msg: str, vram_msg: str, settings: Dict[str, Any]) -> Path:
879
- meta = {
880
- "ok": True,
881
- "job_id": job_id,
882
- "file": str(mp3_path),
883
- "basename": mp3_path.name,
884
- "created": datetime.utcnow().isoformat() + "Z",
885
- "status": status_msg,
886
- "vram": vram_msg,
887
- "release": RELEASE,
888
- "settings": settings
889
- }
890
- meta_path = JSON_DIR / (mp3_path.stem + ".json")
891
- try:
892
- meta_path.write_text(json.dumps(meta, indent=2), encoding="utf-8")
893
- except Exception as e:
894
- logger.error(f"Metadata write failed: {e}")
895
- return meta_path
896
 
 
 
 
897
  @fastapp.post("/render")
898
  def render(req: RenderRequest):
899
  if is_busy():
@@ -901,13 +884,12 @@ def render(req: RenderRequest):
901
  job_id = f"render_{int(time.time())}"
902
  set_busy(True, job_id)
903
  try:
904
- # merge settings (req overrides CURRENT_SETTINGS)
905
  s = CURRENT_SETTINGS.copy()
906
  for k, v in req.dict().items():
907
  if v is not None:
908
  s[k] = v
909
 
910
- mp3, msg, vram = generate_music(
911
  s.get("instrumental_prompt", req.instrumental_prompt),
912
  float(s.get("cfg_scale", DEFAULT_SETTINGS["cfg_scale"])),
913
  int(s.get("top_k", DEFAULT_SETTINGS["top_k"])),
@@ -929,60 +911,26 @@ def render(req: RenderRequest):
929
  str(s.get("bit_depth", DEFAULT_SETTINGS["bit_depth"])),
930
  str(s.get("style", "custom"))
931
  )
932
- if not mp3:
933
- raise HTTPException(status_code=500, detail=msg)
934
-
935
- mp3_path = Path(mp3).resolve()
936
- if not mp3_path.exists() or not mp3_path.is_file():
937
- raise HTTPException(status_code=500, detail="MP3 path missing after render")
938
-
939
- # Save metadata (server-side only)
940
- _write_metadata(mp3_path, job_id, msg, vram, {
941
- k: s.get(k) for k in [
942
- "instrumental_prompt","cfg_scale","top_k","top_p","temperature",
943
- "total_duration","bpm","drum_beat","synthesizer","rhythmic_steps",
944
- "bass_style","guitar_style","target_volume","preset","max_steps",
945
- "bitrate","output_sample_rate","bit_depth","style"
946
- ]
947
- })
948
 
949
- # Return MP3 binary response
 
 
 
 
 
 
 
 
 
950
  return FileResponse(
951
- path=str(mp3_path),
952
  media_type="audio/mpeg",
953
- filename=mp3_path.name,
954
- headers={
955
- "X-Job-ID": job_id,
956
- "X-Status": msg,
957
- "X-VRAM": vram,
958
- "X-Release": RELEASE
959
- }
960
  )
961
  finally:
962
  set_busy(False, None)
963
 
964
- # ---------------------------
965
- # Hourly cleanup (older > 48h)
966
- # ---------------------------
967
- def _cleanup_loop():
968
- keep_hours = int(os.getenv("CLEANUP_HOURS", "48"))
969
- while True:
970
- cutoff = time.time() - keep_hours * 3600
971
- for folder in (MP3_DIR, JSON_DIR, LOG_DIR):
972
- try:
973
- for p in folder.iterdir():
974
- try:
975
- if p.is_file() and p.stat().st_mtime < cutoff:
976
- p.unlink()
977
- logger.info(f"Cleanup: deleted old file {p}")
978
- except Exception as e:
979
- logger.error(f"Cleanup error for {p}: {e}")
980
- except Exception as e:
981
- logger.error(f"Cleanup listdir error in {folder}: {e}")
982
- time.sleep(3600)
983
-
984
- threading.Thread(target=_cleanup_loop, daemon=True).start()
985
-
986
  def _start_fastapi():
987
  uvicorn.run(fastapp, host="0.0.0.0", port=8555, log_level="info")
988
 
@@ -1101,7 +1049,7 @@ with gr.Blocks(css=read_css(), analytics_enabled=False, title=f"GhostAI Music Ge
1101
  bitrate_state = gr.State(value=str(loaded.get("bitrate", "192k")))
1102
  sample_rate_state = gr.State(value=str(loaded.get("output_sample_rate", "48000")))
1103
  bit_depth_state = gr.State(value=str(loaded.get("bit_depth", "16")))
1104
- selected_style = gr.State(value=str(loaded.get("style", "custom"))) # style for filename
1105
 
1106
  with gr.Row():
1107
  bitrate_128_btn = gr.Button("Bitrate 128k", variant="secondary")
@@ -1169,7 +1117,7 @@ with gr.Blocks(css=read_css(), analytics_enabled=False, title=f"GhostAI Music Ge
1169
  new_steps,
1170
  new_bass,
1171
  new_guitar,
1172
- style_key
1173
  )
1174
 
1175
  for key, btn in row1 + row2 + row3 + row4:
@@ -1191,7 +1139,7 @@ with gr.Blocks(css=read_css(), analytics_enabled=False, title=f"GhostAI Music Ge
1191
  bit_depth_16_btn.click(lambda: "16", outputs=bit_depth_state)
1192
  bit_depth_24_btn.click(lambda: "24", outputs=bit_depth_state)
1193
 
1194
- # Generate (pass style)
1195
  gen_btn.click(
1196
  generate_music_wrapper,
1197
  inputs=[
 
1
+ # app.py
2
  #!/usr/bin/env python3
3
  # -*- coding: utf-8 -*-
4
 
5
  # GhostAI Music Generator — Release v1.3.3
6
  # Gradio UI + FastAPI server, externalized styles (CSS), prompts (INI), and examples (MD).
7
  # Saves MP3s to ./mp3, single rotating log (max 5MB) in ./logs, colorized console.
 
 
 
 
8
 
9
  import os
10
  import sys
 
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
 
41
  from colorama import init as colorama_init, Fore
 
42
 
43
  RELEASE = "v1.3.3"
44
 
 
60
  torch.backends.cudnn.deterministic = True
61
 
62
  BASE_DIR = Path(__file__).parent.resolve()
63
+ LOG_DIR = BASE_DIR / "logs"
64
+ MP3_DIR = BASE_DIR / "mp3"
 
 
65
  LOG_DIR.mkdir(parents=True, exist_ok=True)
66
  MP3_DIR.mkdir(parents=True, exist_ok=True)
 
67
 
68
  LOG_FILE = LOG_DIR / "ghostai_musicgen.log"
69
  logger = logging.getLogger("ghostai-musicgen")
 
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(
 
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():
 
884
  job_id = f"render_{int(time.time())}"
885
  set_busy(True, job_id)
886
  try:
 
887
  s = CURRENT_SETTINGS.copy()
888
  for k, v in req.dict().items():
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
  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
  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")
 
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:
 
1139
  bit_depth_16_btn.click(lambda: "16", outputs=bit_depth_state)
1140
  bit_depth_24_btn.click(lambda: "24", outputs=bit_depth_state)
1141
 
1142
+ # Generate (pass style for filename)
1143
  gen_btn.click(
1144
  generate_music_wrapper,
1145
  inputs=[