Update public/apibinararybuild.py
Browse files- 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
|
| 68 |
-
MP3_DIR
|
| 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 #
|
| 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 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
|
| 882 |
-
|
| 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 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 950 |
return FileResponse(
|
| 951 |
-
path=
|
| 952 |
media_type="audio/mpeg",
|
| 953 |
-
filename=
|
| 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=[
|