[GH-ISSUE #49] after change to gradio: fix: to make the ui work again with ace-step-api #43

Open
opened 2026-02-26 21:31:00 +03:00 by kerem · 0 comments
Owner

Originally created by @Basthet on GitHub (Feb 11, 2026).
Original GitHub issue: https://github.com/fspecii/ace-step-ui/issues/49

here is my diff after npm install gradio:

diff --git a/server/src/services/acestep-api.ts b/server/src/services/acestep-api.ts
new file mode 100644
index 0000000..5ff771b
--- /dev/null
+++ b/server/src/services/acestep-api.ts
@@ -0,0 +1,157 @@
+import { config } from '../config/index.js';
+import { GenerationParams } from './acestep.js';
+
+const ACESTEP_API_URL = config.acestep.apiUrl;
+
+// --- Request/Response Models (OpenAI Compatible, simplified for our use case) ---
+
+interface ChatMessage {
+  role: string;
+  content: string | Array<{ type: string; text?: string; input_audio?: { data: string; format: string } }>;
+}
+
+interface AudioConfig {
+  duration?: number;
+  format?: string;
+  bpm?: number;
+  key_scale?: string;
+  time_signature?: string;
+  vocal_language?: string;
+  instrumental?: boolean;
+}
+
+interface ChatCompletionRequest {
+  model: string;
+  messages: ChatMessage[];
+  audio_config?: AudioConfig;
+  stream?: boolean;
+  temperature?: number;
+  top_p?: number;
+  seed?: number | string;
+  thinking?: boolean;
+  guidance_scale?: number;
+  batch_size?: number;
+  lyrics?: string;
+  sample_mode?: boolean;
+  use_format?: boolean;
+  use_cot_caption?: boolean;
+  use_cot_language?: boolean;
+  task_type?: string;
+  repainting_start?: number;
+  repainting_end?: number;
+  audio_cover_strength?: number;
+}
+
+interface AudioUrlContent {
+  url: string;
+}
+
+interface AudioOutputItem {
+  type: string;
+  audio_url: AudioUrlContent;
+}
+
+interface ResponseMessage {
+  role: string;
+  content?: string;
+  audio?: AudioOutputItem[];
+}
+
+interface Choice {
+  index: number;
+  message: ResponseMessage;
+  finish_reason: string;
+}
+
+interface ChatCompletionResponse {
+  id: string;
+  object: string;
+  created: number;
+  model: string;
+  choices: Choice[];
+  usage: {
+    prompt_tokens: number;
+    completion_tokens: number;
+    total_tokens: number;
+  };
+}
+
+// --- ACE-Step API Service ---
+
+export async function generateMusicWithAceStepApi(
+  params: GenerationParams,
+): Promise<ChatCompletionResponse> {
+  const messages: ChatMessage[] = [];
+  let promptContent = '';
+
+  if (params.customMode) {
+    promptContent = `<prompt>${params.style}</prompt>`;
+    if (params.lyrics) {
+      promptContent += `<lyrics>${params.lyrics}</lyrics>`;
+    }
+  } else if (params.songDescription) {
+    promptContent = params.songDescription;
+  } else if (params.style) {
+    promptContent = params.style;
+  }
+
+  messages.push({ role: 'user', content: promptContent });
+
+  const audioConfig: AudioConfig = {
+    duration: params.duration,
+    format: params.audioFormat,
+    bpm: params.bpm,
+    key_scale: params.keyScale,
+    time_signature: params.timeSignature,
+    vocal_language: params.vocalLanguage,
+    instrumental: params.instrumental,
+  };
+
+  const requestBody: ChatCompletionRequest = {
+    model: 'acemusic/acestep-v1.5-turbo', // Hardcode for now, can be dynamic later
+    messages: messages,
+    audio_config: audioConfig,
+    temperature: params.lmTemperature,
+    top_p: params.lmTopP,
+    seed: params.randomSeed === false && params.seed !== undefined ? params.seed : undefined,
+    thinking: params.thinking,
+    guidance_scale: params.guidanceScale,
+    batch_size: params.batchSize,
+    lyrics: params.lyrics, // This might be redundant if lyrics are in messages, but good to pass
+    sample_mode: !params.customMode && !!params.songDescription, // If not custom mode and has song description, it's sample mode
+    use_format: params.isFormatCaption,
+    use_cot_caption: params.useCotCaption,
+    use_cot_language: params.useCotLanguage,
+    task_type: params.taskType,
+    repainting_start: params.repaintingStart,
+    repainting_end: params.repaintingEnd,
+    audio_cover_strength: params.audioCoverStrength,
+  };
+
+  // Handle reference and source audio
+  if (params.referenceAudioUrl || params.sourceAudioUrl) {
+    const audioParts: Array<{ type: string; input_audio: { data: string; format: string } }> = [];
+    // NOTE: The openrouter_api_server expects base64 encoded audio data in the messages content
+    // For simplicity, we'll assume the URLs are local paths that can be read and converted to base64.
+    // In a real-world scenario, you might need a more robust way to handle remote URLs or large files.
+    // For now, we'll skip sending audio data via messages and rely on the server's ability to handle paths if it does.
+    // If the server expects base64, this part needs to be implemented.
+    console.warn("Sending audio data via messages is not yet implemented. Reference/source audio might not work as expected.");
+  }
+
+  const response = await fetch(`${ACESTEP_API_URL}/v1/chat/completions`, {
+    method: 'POST',
+    headers: {
+      'Content-Type': 'application/json',
+      // Add API key if needed, from config.acestep.apiKey
+    },
+    body: JSON.stringify(requestBody),
+  });
+
+  if (!response.ok) {
+    const errorData = await response.json();
+    throw new Error(`ACE-Step API error: ${response.status} - ${errorData.detail || response.statusText}`);
+  }
+
+  return response.json();
+}
diff --git a/server/src/services/acestep.ts b/server/src/services/acestep.ts
index 2acd633..b428c84 100644
--- a/server/src/services/acestep.ts
+++ b/server/src/services/acestep.ts
@@ -20,7 +20,8 @@ function getAudioDuration(filePath: string): number {
 }
 import { fileURLToPath } from 'url';
 import { config } from '../config/index.js';
-import { getGradioClient, resetGradioClient, isGradioAvailable } from './gradio-client.js';
+
+import { generateMusicWithAceStepApi } from './acestep-api.js';
 
 const __filename = fileURLToPath(import.meta.url);
 const __dirname = path.dirname(__filename);
@@ -28,6 +29,84 @@ const AUDIO_DIR = path.join(__dirname, '../../public/audio');
 
 const ACESTEP_API = config.acestep.apiUrl;
 
+// ... (rest of the file)
+
+async function processGenerationViaAceStepApi(
+  jobId: string,
+  params: GenerationParams,
+  job: ActiveJob,
+): Promise<void> {
+  console.log(`Job ${jobId}: Using ACE-Step API for generation`, {
+    prompt: (params.songDescription || params.style || '').slice(0, 50),
+    duration: params.duration,
+    batchSize: params.batchSize,
+  });
+
+  job.stage = 'Generating music via ACE-Step API...';
+
+  try {
+    const response = await generateMusicWithAceStepApi(params);
+
+    if (!response || !response.choices || response.choices.length === 0) {
+      throw new Error('ACE-Step API returned no choices');
+    }
+
+    const firstChoice = response.choices[0];
+    const audioOutputs = firstChoice.message.audio;
+
+    if (!audioOutputs || audioOutputs.length === 0) {
+      throw new Error('ACE-Step API returned no audio outputs');
+    }
+
+    const audioUrls: string[] = [];
+    let actualDuration = 0;
+
+    for (const audioOutput of audioOutputs) {
+      const remoteUrl = audioOutput.audio_url.url;
+      if (remoteUrl) {
+        const ext = remoteUrl.includes('.flac') ? '.flac' : '.mp3'; // Assuming mp3 or flac
+        const filename = `${jobId}_${audioUrls.length}${ext}`;
+        const destPath = path.join(AUDIO_DIR, filename);
+
+        // Download the audio file from the remote URL
+        const audioResponse = await fetch(remoteUrl);
+        if (!audioResponse.ok) {
+          throw new Error(`Failed to download audio from ${remoteUrl}: ${audioResponse.statusText}`);
+        }
+        const buffer = await audioResponse.arrayBuffer();
+        await writeFile(destPath, Buffer.from(buffer));
+
+        if (audioUrls.length === 0) {
+          actualDuration = getAudioDuration(destPath);
+        }
+        audioUrls.push(`/audio/${filename}`);
+      }
+    }
+
+    const finalDuration = actualDuration > 0
+      ? actualDuration
+      : (params.duration || 60);
+
+    job.status = 'succeeded';
+    job.result = {
+      audioUrls,
+      duration: finalDuration,
+      bpm: params.bpm, // Assuming API response doesn't provide this directly, use original
+      keyScale: params.keyScale,
+      timeSignature: params.timeSignature,
+      status: 'succeeded',
+    };
+    job.rawResponse = response;
+    console.log(`Job ${jobId}: Completed via ACE-Step API with ${audioUrls.length} audio files`);
+
+  } catch (error) {
+    console.error(`Job ${jobId}: ACE-Step API generation failed:`, error);
+    job.status = 'failed';
+    job.error = error instanceof Error ? error.message : 'Generation failed';
+  }
+}
+
+
 // Resolve ACE-Step path (from env or default relative path)
 function resolveAceStepPath(): string {
   const envPath = process.env.ACESTEP_PATH;
@@ -359,7 +438,9 @@ let isProcessingQueue = false;
 
 // Health check - verify Gradio app is reachable
 export async function checkSpaceHealth(): Promise<boolean> {
-  return isGradioAvailable();
+  // For now, assume API is healthy if URL is configured.
+  // A more robust check would ping a /health endpoint on the ACE-Step API.
+  return !!ACESTEP_API;
 }
 
 // Discover endpoints (for compatibility)
@@ -369,7 +450,7 @@ export async function discoverEndpoints(): Promise<unknown> {
 
 // Reset client — forces Gradio reconnection on next request
 export function resetClient(): void {
-  resetGradioClient();
+  // No client to reset as we are directly calling the API
 }
 
 // ---------------------------------------------------------------------------
@@ -448,121 +529,20 @@ async function processGeneration(
     return;
   }
 
-  // Try Gradio first
-  const gradioUp = await isGradioAvailable();
-  if (gradioUp) {
-    try {
-      await processGenerationViaGradio(jobId, params, job);
-      return;
-    } catch (error) {
-      console.error(`Job ${jobId}: Gradio generation failed, trying Python spawn fallback`, error);
-      // Fall through to Python spawn
-    }
+  // Try ACE-Step API first
+  try {
+    await processGenerationViaAceStepApi(jobId, params, job);
+    return;
+  } catch (error) {
+    console.error(`Job ${jobId}: ACE-Step API generation failed, trying Python spawn fallback`, error);
+    // Fall through to Python spawn
   }
 
   // Fallback: Python spawn
   await processGenerationViaPython(jobId, params, job);
 }
 
-async function processGenerationViaGradio(
-  jobId: string,
-  params: GenerationParams,
-  job: ActiveJob,
-): Promise<void> {
-  const client = await getGradioClient();
-  const args = await buildGradioArgs(params);
-
-  const caption = params.style || 'pop music';
-  const prompt = params.customMode ? caption : (params.songDescription || caption);
-
-  console.log(`Job ${jobId}: Using Gradio /generation_wrapper`, {
-    prompt: prompt.slice(0, 50),
-    duration: params.duration,
-    batchSize: params.batchSize,
-  });
-
-  job.stage = 'Generating music via Gradio...';
-
-  // predict() blocks until generation is complete
-  const result = await client.predict('/generation_wrapper', args);
-  const data = result.data as unknown[];
 
-  if (!Array.isArray(data) || data.length === 0) {
-    throw new Error(`Gradio returned unexpected data format: ${typeof data}`);
-  }
-
-  // Extract audio files from the result
-  // Outputs 0-7: individual audio samples (filepath objects)
-  // Output 8: "All Generated Files" as list[filepath]
-  // Output 9: "Generation Details" (string)
-  // Output 10: "Generation Status" (string)
-  // Output 11: "Seed" (string)
-  const allFiles = data[8]; // list of file objects
-  const genDetails = data[9] as string | undefined;
-  const genStatus = data[10] as string | undefined;
-
-  // Collect audio file objects — prefer the "All Generated Files" list
-  let audioFileObjects: Array<{ url?: string; path?: string; orig_name?: string }> = [];
-
-  if (Array.isArray(allFiles) && allFiles.length > 0) {
-    audioFileObjects = allFiles.filter(
-      (f: any) => f && (f.path || f.url) && isAudioFile(f.orig_name || f.path || '')
-    );
-  }
-
-  // Fallback: check individual sample outputs (indices 0-7)
-  if (audioFileObjects.length === 0) {
-    for (let i = 0; i < 8; i++) {
-      const fileObj = data[i] as any;
-      if (fileObj && (fileObj.path || fileObj.url)) {
-        audioFileObjects.push(fileObj);
-      }
-    }
-  }
-
-  if (audioFileObjects.length === 0) {
-    throw new Error(`Gradio generation returned no audio files. Status: ${genStatus || 'unknown'}. Details: ${genDetails || 'none'}`);
-  }
-
-  // Download audio files to local storage
-  const audioUrls: string[] = [];
-  let actualDuration = 0;
-  const audioFormat = params.audioFormat ?? 'mp3';
-
-  for (const fileObj of audioFileObjects) {
-    const origName = fileObj.orig_name || fileObj.path || '';
-    const ext = origName.includes('.flac') ? '.flac' : `.${audioFormat}`;
-    const filename = `${jobId}_${audioUrls.length}${ext}`;
-    const destPath = path.join(AUDIO_DIR, filename);
-
-    await downloadGradioAudioFile(fileObj, destPath);
-
-    if (audioUrls.length === 0) {
-      actualDuration = getAudioDuration(destPath);
-    }
-
-    audioUrls.push(`/audio/${filename}`);
-  }
-
-  // Parse metadata from generation details if available
-  const metas = parseGenerationDetails(genDetails);
-
-  const finalDuration = actualDuration > 0
-    ? actualDuration
-    : (metas.duration || params.duration || 60);
-
-  job.status = 'succeeded';
-  job.result = {
-    audioUrls,
-    duration: finalDuration,
-    bpm: metas.bpm || params.bpm,
-    keyScale: metas.keyScale || params.keyScale,
-    timeSignature: metas.timeSignature || params.timeSignature,
-    status: 'succeeded',
-  };
-  job.rawResponse = { genDetails, genStatus };
-  console.log(`Job ${jobId}: Completed via Gradio with ${audioUrls.length} audio files`);
-}
 
 function isAudioFile(name: string): boolean {
   return /\.(mp3|flac|wav|ogg|m4a)$/i.test(name);
Originally created by @Basthet on GitHub (Feb 11, 2026). Original GitHub issue: https://github.com/fspecii/ace-step-ui/issues/49 here is my diff after npm install gradio: ``` diff --git a/server/src/services/acestep-api.ts b/server/src/services/acestep-api.ts new file mode 100644 index 0000000..5ff771b --- /dev/null +++ b/server/src/services/acestep-api.ts @@ -0,0 +1,157 @@ +import { config } from '../config/index.js'; +import { GenerationParams } from './acestep.js'; + +const ACESTEP_API_URL = config.acestep.apiUrl; + +// --- Request/Response Models (OpenAI Compatible, simplified for our use case) --- + +interface ChatMessage { + role: string; + content: string | Array<{ type: string; text?: string; input_audio?: { data: string; format: string } }>; +} + +interface AudioConfig { + duration?: number; + format?: string; + bpm?: number; + key_scale?: string; + time_signature?: string; + vocal_language?: string; + instrumental?: boolean; +} + +interface ChatCompletionRequest { + model: string; + messages: ChatMessage[]; + audio_config?: AudioConfig; + stream?: boolean; + temperature?: number; + top_p?: number; + seed?: number | string; + thinking?: boolean; + guidance_scale?: number; + batch_size?: number; + lyrics?: string; + sample_mode?: boolean; + use_format?: boolean; + use_cot_caption?: boolean; + use_cot_language?: boolean; + task_type?: string; + repainting_start?: number; + repainting_end?: number; + audio_cover_strength?: number; +} + +interface AudioUrlContent { + url: string; +} + +interface AudioOutputItem { + type: string; + audio_url: AudioUrlContent; +} + +interface ResponseMessage { + role: string; + content?: string; + audio?: AudioOutputItem[]; +} + +interface Choice { + index: number; + message: ResponseMessage; + finish_reason: string; +} + +interface ChatCompletionResponse { + id: string; + object: string; + created: number; + model: string; + choices: Choice[]; + usage: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }; +} + +// --- ACE-Step API Service --- + +export async function generateMusicWithAceStepApi( + params: GenerationParams, +): Promise<ChatCompletionResponse> { + const messages: ChatMessage[] = []; + let promptContent = ''; + + if (params.customMode) { + promptContent = `<prompt>${params.style}</prompt>`; + if (params.lyrics) { + promptContent += `<lyrics>${params.lyrics}</lyrics>`; + } + } else if (params.songDescription) { + promptContent = params.songDescription; + } else if (params.style) { + promptContent = params.style; + } + + messages.push({ role: 'user', content: promptContent }); + + const audioConfig: AudioConfig = { + duration: params.duration, + format: params.audioFormat, + bpm: params.bpm, + key_scale: params.keyScale, + time_signature: params.timeSignature, + vocal_language: params.vocalLanguage, + instrumental: params.instrumental, + }; + + const requestBody: ChatCompletionRequest = { + model: 'acemusic/acestep-v1.5-turbo', // Hardcode for now, can be dynamic later + messages: messages, + audio_config: audioConfig, + temperature: params.lmTemperature, + top_p: params.lmTopP, + seed: params.randomSeed === false && params.seed !== undefined ? params.seed : undefined, + thinking: params.thinking, + guidance_scale: params.guidanceScale, + batch_size: params.batchSize, + lyrics: params.lyrics, // This might be redundant if lyrics are in messages, but good to pass + sample_mode: !params.customMode && !!params.songDescription, // If not custom mode and has song description, it's sample mode + use_format: params.isFormatCaption, + use_cot_caption: params.useCotCaption, + use_cot_language: params.useCotLanguage, + task_type: params.taskType, + repainting_start: params.repaintingStart, + repainting_end: params.repaintingEnd, + audio_cover_strength: params.audioCoverStrength, + }; + + // Handle reference and source audio + if (params.referenceAudioUrl || params.sourceAudioUrl) { + const audioParts: Array<{ type: string; input_audio: { data: string; format: string } }> = []; + // NOTE: The openrouter_api_server expects base64 encoded audio data in the messages content + // For simplicity, we'll assume the URLs are local paths that can be read and converted to base64. + // In a real-world scenario, you might need a more robust way to handle remote URLs or large files. + // For now, we'll skip sending audio data via messages and rely on the server's ability to handle paths if it does. + // If the server expects base64, this part needs to be implemented. + console.warn("Sending audio data via messages is not yet implemented. Reference/source audio might not work as expected."); + } + + const response = await fetch(`${ACESTEP_API_URL}/v1/chat/completions`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + // Add API key if needed, from config.acestep.apiKey + }, + body: JSON.stringify(requestBody), + }); + + if (!response.ok) { + const errorData = await response.json(); + throw new Error(`ACE-Step API error: ${response.status} - ${errorData.detail || response.statusText}`); + } + + return response.json(); +} diff --git a/server/src/services/acestep.ts b/server/src/services/acestep.ts index 2acd633..b428c84 100644 --- a/server/src/services/acestep.ts +++ b/server/src/services/acestep.ts @@ -20,7 +20,8 @@ function getAudioDuration(filePath: string): number { } import { fileURLToPath } from 'url'; import { config } from '../config/index.js'; -import { getGradioClient, resetGradioClient, isGradioAvailable } from './gradio-client.js'; + +import { generateMusicWithAceStepApi } from './acestep-api.js'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -28,6 +29,84 @@ const AUDIO_DIR = path.join(__dirname, '../../public/audio'); const ACESTEP_API = config.acestep.apiUrl; +// ... (rest of the file) + +async function processGenerationViaAceStepApi( + jobId: string, + params: GenerationParams, + job: ActiveJob, +): Promise<void> { + console.log(`Job ${jobId}: Using ACE-Step API for generation`, { + prompt: (params.songDescription || params.style || '').slice(0, 50), + duration: params.duration, + batchSize: params.batchSize, + }); + + job.stage = 'Generating music via ACE-Step API...'; + + try { + const response = await generateMusicWithAceStepApi(params); + + if (!response || !response.choices || response.choices.length === 0) { + throw new Error('ACE-Step API returned no choices'); + } + + const firstChoice = response.choices[0]; + const audioOutputs = firstChoice.message.audio; + + if (!audioOutputs || audioOutputs.length === 0) { + throw new Error('ACE-Step API returned no audio outputs'); + } + + const audioUrls: string[] = []; + let actualDuration = 0; + + for (const audioOutput of audioOutputs) { + const remoteUrl = audioOutput.audio_url.url; + if (remoteUrl) { + const ext = remoteUrl.includes('.flac') ? '.flac' : '.mp3'; // Assuming mp3 or flac + const filename = `${jobId}_${audioUrls.length}${ext}`; + const destPath = path.join(AUDIO_DIR, filename); + + // Download the audio file from the remote URL + const audioResponse = await fetch(remoteUrl); + if (!audioResponse.ok) { + throw new Error(`Failed to download audio from ${remoteUrl}: ${audioResponse.statusText}`); + } + const buffer = await audioResponse.arrayBuffer(); + await writeFile(destPath, Buffer.from(buffer)); + + if (audioUrls.length === 0) { + actualDuration = getAudioDuration(destPath); + } + audioUrls.push(`/audio/${filename}`); + } + } + + const finalDuration = actualDuration > 0 + ? actualDuration + : (params.duration || 60); + + job.status = 'succeeded'; + job.result = { + audioUrls, + duration: finalDuration, + bpm: params.bpm, // Assuming API response doesn't provide this directly, use original + keyScale: params.keyScale, + timeSignature: params.timeSignature, + status: 'succeeded', + }; + job.rawResponse = response; + console.log(`Job ${jobId}: Completed via ACE-Step API with ${audioUrls.length} audio files`); + + } catch (error) { + console.error(`Job ${jobId}: ACE-Step API generation failed:`, error); + job.status = 'failed'; + job.error = error instanceof Error ? error.message : 'Generation failed'; + } +} + + // Resolve ACE-Step path (from env or default relative path) function resolveAceStepPath(): string { const envPath = process.env.ACESTEP_PATH; @@ -359,7 +438,9 @@ let isProcessingQueue = false; // Health check - verify Gradio app is reachable export async function checkSpaceHealth(): Promise<boolean> { - return isGradioAvailable(); + // For now, assume API is healthy if URL is configured. + // A more robust check would ping a /health endpoint on the ACE-Step API. + return !!ACESTEP_API; } // Discover endpoints (for compatibility) @@ -369,7 +450,7 @@ export async function discoverEndpoints(): Promise<unknown> { // Reset client — forces Gradio reconnection on next request export function resetClient(): void { - resetGradioClient(); + // No client to reset as we are directly calling the API } // --------------------------------------------------------------------------- @@ -448,121 +529,20 @@ async function processGeneration( return; } - // Try Gradio first - const gradioUp = await isGradioAvailable(); - if (gradioUp) { - try { - await processGenerationViaGradio(jobId, params, job); - return; - } catch (error) { - console.error(`Job ${jobId}: Gradio generation failed, trying Python spawn fallback`, error); - // Fall through to Python spawn - } + // Try ACE-Step API first + try { + await processGenerationViaAceStepApi(jobId, params, job); + return; + } catch (error) { + console.error(`Job ${jobId}: ACE-Step API generation failed, trying Python spawn fallback`, error); + // Fall through to Python spawn } // Fallback: Python spawn await processGenerationViaPython(jobId, params, job); } -async function processGenerationViaGradio( - jobId: string, - params: GenerationParams, - job: ActiveJob, -): Promise<void> { - const client = await getGradioClient(); - const args = await buildGradioArgs(params); - - const caption = params.style || 'pop music'; - const prompt = params.customMode ? caption : (params.songDescription || caption); - - console.log(`Job ${jobId}: Using Gradio /generation_wrapper`, { - prompt: prompt.slice(0, 50), - duration: params.duration, - batchSize: params.batchSize, - }); - - job.stage = 'Generating music via Gradio...'; - - // predict() blocks until generation is complete - const result = await client.predict('/generation_wrapper', args); - const data = result.data as unknown[]; - if (!Array.isArray(data) || data.length === 0) { - throw new Error(`Gradio returned unexpected data format: ${typeof data}`); - } - - // Extract audio files from the result - // Outputs 0-7: individual audio samples (filepath objects) - // Output 8: "All Generated Files" as list[filepath] - // Output 9: "Generation Details" (string) - // Output 10: "Generation Status" (string) - // Output 11: "Seed" (string) - const allFiles = data[8]; // list of file objects - const genDetails = data[9] as string | undefined; - const genStatus = data[10] as string | undefined; - - // Collect audio file objects — prefer the "All Generated Files" list - let audioFileObjects: Array<{ url?: string; path?: string; orig_name?: string }> = []; - - if (Array.isArray(allFiles) && allFiles.length > 0) { - audioFileObjects = allFiles.filter( - (f: any) => f && (f.path || f.url) && isAudioFile(f.orig_name || f.path || '') - ); - } - - // Fallback: check individual sample outputs (indices 0-7) - if (audioFileObjects.length === 0) { - for (let i = 0; i < 8; i++) { - const fileObj = data[i] as any; - if (fileObj && (fileObj.path || fileObj.url)) { - audioFileObjects.push(fileObj); - } - } - } - - if (audioFileObjects.length === 0) { - throw new Error(`Gradio generation returned no audio files. Status: ${genStatus || 'unknown'}. Details: ${genDetails || 'none'}`); - } - - // Download audio files to local storage - const audioUrls: string[] = []; - let actualDuration = 0; - const audioFormat = params.audioFormat ?? 'mp3'; - - for (const fileObj of audioFileObjects) { - const origName = fileObj.orig_name || fileObj.path || ''; - const ext = origName.includes('.flac') ? '.flac' : `.${audioFormat}`; - const filename = `${jobId}_${audioUrls.length}${ext}`; - const destPath = path.join(AUDIO_DIR, filename); - - await downloadGradioAudioFile(fileObj, destPath); - - if (audioUrls.length === 0) { - actualDuration = getAudioDuration(destPath); - } - - audioUrls.push(`/audio/${filename}`); - } - - // Parse metadata from generation details if available - const metas = parseGenerationDetails(genDetails); - - const finalDuration = actualDuration > 0 - ? actualDuration - : (metas.duration || params.duration || 60); - - job.status = 'succeeded'; - job.result = { - audioUrls, - duration: finalDuration, - bpm: metas.bpm || params.bpm, - keyScale: metas.keyScale || params.keyScale, - timeSignature: metas.timeSignature || params.timeSignature, - status: 'succeeded', - }; - job.rawResponse = { genDetails, genStatus }; - console.log(`Job ${jobId}: Completed via Gradio with ${audioUrls.length} audio files`); -} function isAudioFile(name: string): boolean { return /\.(mp3|flac|wav|ogg|m4a)$/i.test(name); ```
Sign in to join this conversation.
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
starred/ace-step-ui#43
No description provided.