Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 13 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,24 @@

## Bundled acestep.cpp (v0.0.3)

`bun run build` downloads the correct asset from **[acestep.cpp releases v0.0.3](https://github.com/audiohacking/acestep.cpp/releases/tag/v0.0.3)** for the **current** OS/arch, installs them under `acestep-runtime/bin/`, compiles `dist/acestep-api`, then copies `acestep-runtime` next to the executable:
`bun run build` downloads the correct asset from **[acestep.cpp releases v0.0.3](https://github.com/audiohacking/acestep.cpp/releases/tag/v0.0.3)** for the **current** OS/arch, installs the **full archive contents** under `acestep-runtime/bin/`, compiles `dist/acestep-api`, then copies `acestep-runtime` next to the executable.

The prebuilt archives include executables and all shared libraries needed to run them:

```text
dist/
acestep-api # or acestep-api.exe
acestep-api # or acestep-api.exe
acestep-runtime/
bin/
ace-lm
ace-synth
ace-lm # 5Hz LM (text + lyrics → audio codes)
ace-synth # DiT + VAE (audio codes → audio)
ace-server # standalone HTTP server
ace-understand # reverse: audio → metadata
neural-codec # VAE encode/decode utility
mp3-codec # MP3 encoder/decoder utility
quantize # GGUF requantizer
libggml*.so / *.dylib # GGML shared libraries (Linux / macOS)
*.dll # GGML DLLs (Windows)
```

Run the API **from `dist/`** (or anywhere) — the binary resolves siblings via `dirname(execPath)`:
Expand Down
11 changes: 10 additions & 1 deletion docs/API.md
Original file line number Diff line number Diff line change
Expand Up @@ -552,9 +552,18 @@ curl "http://localhost:8001/v1/audio?path=%2Fabc123.mp3" -o output.mp3

### 11.2 Response

Runs `ace-synth` without arguments (which prints its usage and exits non-zero) to confirm the binary is present and executable. The `binary` field is `"ok"` when the binary starts successfully, or `"unavailable"` when it cannot be found or run.

```json
{
"data": { "status": "ok", "service": "ACE-Step API", "version": "1.0" },
"data": {
"status": "ok",
"service": "ACE-Step API",
"version": "1.0",
"binary": "ok",
"binary_path": "/path/to/acestep-runtime/bin/ace-synth",
"binary_hint": "Usage: ace-synth --request <json...> ..."
},
"code": 200,
"error": null,
"timestamp": 1700000000000,
Expand Down
32 changes: 22 additions & 10 deletions scripts/bundle-acestep.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
#!/usr/bin/env bun
/**
* Downloads acestep.cpp v0.0.3 release binaries for the current OS/arch and
* installs them under <repo>/acestep-runtime/bin (ace-lm, ace-synth).
* installs the full archive contents under <repo>/acestep-runtime/bin
* (ace-lm, ace-synth, ace-server, ace-understand, neural-codec, mp3-codec,
* quantize, and all shared libraries).
*
* @see https://github.com/audiohacking/acestep.cpp/releases/tag/v0.0.3
* @see https://github.com/audiohacking/acestep.cpp/blob/master/README.md
Expand Down Expand Up @@ -89,25 +91,35 @@ async function main() {
const all = await walkFiles(extractRoot);
const wantLm = process.platform === "win32" ? "ace-lm.exe" : "ace-lm";
const wantSynth = process.platform === "win32" ? "ace-synth.exe" : "ace-synth";
let lm = all.find((p) => (p.split(/[/\\]/).pop() ?? "") === wantLm);
let synth = all.find((p) => (p.split(/[/\\]/).pop() ?? "") === wantSynth);
const lm = all.find((p) => (p.split(/[/\\]/).pop() ?? "") === wantLm);
const synth = all.find((p) => (p.split(/[/\\]/).pop() ?? "") === wantSynth);
if (!lm || !synth) {
throw new Error(`Could not find ${wantLm} / ${wantSynth} under ${extractRoot}`);
}

await rm(outBin, { recursive: true, force: true });
await mkdir(outBin, { recursive: true });
const outLm = join(outBin, wantLm);
const outSynth = join(outBin, wantSynth);
await copyFile(lm, outLm);
await copyFile(synth, outSynth);

// Copy every file from the archive root so that shared libraries
// (libggml*.so / *.dylib / *.dll) and helper binaries are all present.
const installed: string[] = [];
for (const srcPath of all) {
const name = srcPath.split(/[/\\]/).pop() ?? "";
const destPath = join(outBin, name);
await copyFile(srcPath, destPath);
installed.push(destPath);
}

if (process.platform !== "win32") {
await chmod(outLm, 0o755);
await chmod(outSynth, 0o755);
// Make all regular files (not static libs) executable so every binary works.
for (const destPath of installed) {
if (!destPath.endsWith(".a")) {
await chmod(destPath, 0o755);
}
}
}

console.log(`[bundle-acestep] Installed:\n ${outLm}\n ${outSynth}`);
console.log(`[bundle-acestep] Installed ${installed.length} file(s) to ${outBin}:\n ${installed.map((p) => p.split(/[/\\]/).pop()).join("\n ")}`);
}

main().catch((e) => {
Expand Down
30 changes: 29 additions & 1 deletion src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { mkdir } from "fs/promises";
import { join, resolve } from "path";
import { existsSync } from "fs";
import { config } from "./config";
import { requireAuth } from "./auth";
import { jsonRes } from "./res";
Expand All @@ -12,6 +13,25 @@ import { isPathWithin } from "./paths";

const AUDIO_PATH_PREFIX = "/";

/** Run ace-synth with no arguments to confirm the binary is present and executable. */
async function probeAceSynth(): Promise<{ ok: boolean; path: string; hint: string }> {
const binDir = config.acestepBinDir;
const bin = join(binDir, process.platform === "win32" ? "ace-synth.exe" : "ace-synth");
if (!existsSync(bin)) {
return { ok: false, path: bin, hint: "binary not found" };
}
try {
const proc = Bun.spawn([bin], { stdout: "pipe", stderr: "pipe" });
const [stdout, stderr] = await Promise.all([proc.stdout.text(), proc.stderr.text()]);
await proc.exited;
// ace-synth prints usage and exits non-zero when run with no arguments — that is expected.
const out = (stdout + stderr).trim();
return { ok: true, path: bin, hint: out.slice(0, 300) || "ok" };
} catch (e) {
return { ok: false, path: bin, hint: e instanceof Error ? e.message : String(e) };
}
}

function parsePath(pathParam: string): string {
const decoded = decodeURIComponent(pathParam);
if (decoded.startsWith(AUDIO_PATH_PREFIX)) return decoded;
Expand Down Expand Up @@ -157,7 +177,15 @@ async function handle(req: Request): Promise<Response> {
if (path === "/health" && req.method === "GET") {
const authErr = requireAuth(req.headers.get("Authorization"), undefined);
if (authErr) return authErr;
return jsonRes({ status: "ok", service: "ACE-Step API", version: "1.0" });
const probe = await probeAceSynth();
return jsonRes({
status: "ok",
service: "ACE-Step API",
version: "1.0",
binary: probe.ok ? "ok" : "unavailable",
binary_path: probe.path,
binary_hint: probe.hint,
});
}

if (path === "/v1/models" && req.method === "GET") {
Expand Down
11 changes: 2 additions & 9 deletions src/worker.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { randomUUID } from "crypto";
import { mkdir, writeFile, rename, unlink, readdir, readFile } from "fs/promises";
import { mkdir, writeFile, rename, rm, readdir, readFile } from "fs/promises";
import { join, resolve } from "path";
import { config } from "./config";
import * as store from "./store";
Expand Down Expand Up @@ -356,14 +356,7 @@ export async function runPipeline(taskId: string): Promise<void> {
store.setTaskFailed(taskId, msg, JSON.stringify([failItem]));
} finally {
store.recordJobDuration(Date.now() - started);
try {
const entries = await readdir(jobDir).catch(() => []);
for (const e of entries) {
await unlink(join(jobDir, e)).catch(() => {});
}
} catch {
// ignore
}
await rm(jobDir, { recursive: true, force: true }).catch(() => {});
}
}

Expand Down
Loading