diff --git a/extensions/senseaudio/media-understanding-provider.live.test.ts b/extensions/senseaudio/media-understanding-provider.live.test.ts new file mode 100644 index 000000000000..cc00ce7bf6ed --- /dev/null +++ b/extensions/senseaudio/media-understanding-provider.live.test.ts @@ -0,0 +1,44 @@ +// Senseaudio live tests cover the real speech generation and provider API. +import { spawnSync } from "node:child_process"; +import { readFileSync } from "node:fs"; +import path from "node:path"; +import { runFfmpeg } from "openclaw/plugin-sdk/media-runtime"; +import { withTempDir } from "openclaw/plugin-sdk/test-env"; +import { describe, expect, it } from "vitest"; +import { senseaudioMediaUnderstandingProvider } from "./media-understanding-provider.js"; + +const apiKey = process.env.SENSEAUDIO_API_KEY ?? ""; +const liveEnabled = process.env.OPENCLAW_LIVE_TEST === "1" && apiKey.length > 0; +const hasSay = + liveEnabled && + spawnSync("sh", ["-lc", "command -v say"], { encoding: "utf8", timeout: 5_000 }).status === 0; +const describeLive = liveEnabled && hasSay ? describe : describe.skip; +const transcribeSenseAudioAudio = senseaudioMediaUnderstandingProvider.transcribeAudio; + +if (!transcribeSenseAudioAudio) { + throw new Error("expected SenseAudio transcription capability"); +} + +describeLive("SenseAudio live", () => { + it("transcribes generated speech", async () => { + await withTempDir("openclaw-senseaudio-live-", async (tempDir) => { + const aiffPath = path.join(tempDir, "speech.aiff"); + const mp3Path = path.join(tempDir, "speech.mp3"); + const sayResult = spawnSync("say", ["-o", aiffPath, "open claw live transcription test"], { + encoding: "utf8", + }); + expect(sayResult.status).toBe(0); + await runFfmpeg(["-y", "-i", aiffPath, "-c:a", "libmp3lame", "-b:a", "96k", mp3Path]); + + const result = await transcribeSenseAudioAudio({ + buffer: readFileSync(mp3Path), + fileName: "speech.mp3", + mime: "audio/mpeg", + apiKey, + timeoutMs: 30_000, + }); + + expect(result.text.trim().length).toBeGreaterThan(0); + }); + }, 60_000); +}); diff --git a/extensions/senseaudio/media-understanding-provider.test.ts b/extensions/senseaudio/media-understanding-provider.test.ts index 8ea2252ef5c4..8d1057d8c972 100644 --- a/extensions/senseaudio/media-understanding-provider.test.ts +++ b/extensions/senseaudio/media-understanding-provider.test.ts @@ -1,9 +1,4 @@ // Senseaudio tests cover media understanding provider plugin behavior. -import { spawnSync } from "node:child_process"; -import { mkdtempSync, readFileSync, rmSync } from "node:fs"; -import os from "node:os"; -import path from "node:path"; -import { runFfmpeg } from "openclaw/plugin-sdk/media-runtime"; import { createAuthCaptureJsonFetch, createRequestCaptureJsonFetch, @@ -106,37 +101,4 @@ describe("transcribeSenseAudioAudio", () => { }), ).rejects.toThrow("Audio transcription response missing text"); }); - - it("can transcribe generated speech in live mode", async () => { - if (process.env.OPENCLAW_LIVE_TEST !== "1" || !process.env.SENSEAUDIO_API_KEY) { - return; - } - const say = spawnSync("sh", ["-lc", "command -v say"], { encoding: "utf8" }); - if (say.status !== 0) { - return; - } - - const tempDir = mkdtempSync(path.join(os.tmpdir(), "openclaw-senseaudio-live-")); - try { - const aiffPath = path.join(tempDir, "speech.aiff"); - const mp3Path = path.join(tempDir, "speech.mp3"); - const sayResult = spawnSync("say", ["-o", aiffPath, "open claw live transcription test"], { - encoding: "utf8", - }); - expect(sayResult.status).toBe(0); - await runFfmpeg(["-y", "-i", aiffPath, "-c:a", "libmp3lame", "-b:a", "96k", mp3Path]); - - const result = await transcribeSenseAudioAudio({ - buffer: readFileSync(mp3Path), - fileName: "speech.mp3", - mime: "audio/mpeg", - apiKey: process.env.SENSEAUDIO_API_KEY, - timeoutMs: 30_000, - }); - - expect(result.text.trim().length).toBeGreaterThan(0); - } finally { - rmSync(tempDir, { recursive: true, force: true }); - } - }); }); diff --git a/extensions/tts-local-cli/speech-provider.live.test.ts b/extensions/tts-local-cli/speech-provider.live.test.ts new file mode 100644 index 000000000000..1596f5fc0bba --- /dev/null +++ b/extensions/tts-local-cli/speech-provider.live.test.ts @@ -0,0 +1,59 @@ +// Tts Local Cli live tests cover the real process and ffmpeg integration. +import { readFileSync, writeFileSync } from "node:fs"; +import path from "node:path"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; +import { runFfmpeg } from "openclaw/plugin-sdk/media-runtime"; +import type { SpeechProviderConfig } from "openclaw/plugin-sdk/speech-core"; +import { withTempDir } from "openclaw/plugin-sdk/test-env"; +import { describe, expect, it } from "vitest"; +import { buildCliSpeechProvider } from "./speech-provider.js"; + +const describeLive = process.env.OPENCLAW_LIVE_TEST === "1" ? describe : describe.skip; + +describeLive("buildCliSpeechProvider live", () => { + it("synthesizes through a real local CLI fixture and ffmpeg", async () => { + await withTempDir("openclaw-cli-tts-live-", async (dir) => { + const script = path.join(dir, "copy-audio.mjs"); + const wavPath = path.join(dir, "source.wav"); + await runFfmpeg([ + "-y", + "-f", + "lavfi", + "-i", + "sine=frequency=660:duration=0.1", + "-c:a", + "pcm_s16le", + wavPath, + ]); + writeFileSync( + script, + ` +import { copyFileSync } from "node:fs"; +const outIndex = process.argv.indexOf("--out"); +copyFileSync(${JSON.stringify(wavPath)}, process.argv[outIndex + 1]); +`, + ); + const providerConfig: SpeechProviderConfig = { + command: process.execPath, + args: [script, "--out", "{{OutputPath}}"], + outputFormat: "wav", + timeoutMs: 30_000, + }; + + const result = await buildCliSpeechProvider().synthesize({ + text: "hello world", + cfg: {} as OpenClawConfig, + providerConfig, + providerOverrides: {}, + timeoutMs: 30_000, + target: "voice-note", + }); + + expect(result.outputFormat).toBe("opus"); + expect(result.fileExtension).toBe(".ogg"); + expect(result.voiceCompatible).toBe(true); + expect(result.audioBuffer.byteLength).toBeGreaterThan(0); + expect(readFileSync(wavPath).byteLength).toBeGreaterThan(0); + }); + }, 30_000); +}); diff --git a/extensions/tts-local-cli/speech-provider.test.ts b/extensions/tts-local-cli/speech-provider.test.ts index 2ef67d2ec9e7..1ca2a35800fe 100644 --- a/extensions/tts-local-cli/speech-provider.test.ts +++ b/extensions/tts-local-cli/speech-provider.test.ts @@ -1,5 +1,5 @@ // Tts Local Cli tests cover speech provider plugin behavior. -import { mkdtempSync, readFileSync, rmSync, truncateSync, writeFileSync } from "node:fs"; +import { mkdtempSync, rmSync, truncateSync, writeFileSync } from "node:fs"; import os from "node:os"; import path from "node:path"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; @@ -369,54 +369,4 @@ mkdirSync(process.argv[outIndex + 1]); expect(Buffer.from(preview).toString()).toBe(preview); }, ); - - it("can synthesize through a real local CLI fixture and ffmpeg", async () => { - if (process.env.OPENCLAW_LIVE_TEST !== "1") { - return; - } - const fixture = createCliFixture(); - const rawFfmpeg = await vi.importActual( - "openclaw/plugin-sdk/media-runtime", - ); - runFfmpegMock.mockImplementation(async (args) => { - await rawFfmpeg.runFfmpeg(args); - }); - try { - const wavPath = path.join(fixture.dir, "source.wav"); - await rawFfmpeg.runFfmpeg([ - "-y", - "-f", - "lavfi", - "-i", - "sine=frequency=660:duration=0.1", - "-c:a", - "pcm_s16le", - wavPath, - ]); - writeFileSync( - fixture.script, - ` -import { copyFileSync } from "node:fs"; -const outIndex = process.argv.indexOf("--out"); -copyFileSync(${JSON.stringify(wavPath)}, process.argv[outIndex + 1]); -`, - ); - - const result = await synthesize({ - providerConfig: baseProviderConfig(fixture.script, { - args: [fixture.script, "--out", "{{OutputPath}}"], - outputFormat: "wav", - }), - target: "voice-note", - }); - - expect(result.outputFormat).toBe("opus"); - expect(result.fileExtension).toBe(".ogg"); - expect(result.voiceCompatible).toBe(true); - expect(result.audioBuffer.byteLength).toBeGreaterThan(0); - expect(readFileSync(wavPath).byteLength).toBeGreaterThan(0); - } finally { - rmSync(fixture.dir, { recursive: true, force: true }); - } - }); });