@effect-uai/elevenlabs 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/dist/ElevenLabsSynthesizer.d.mts +58 -0
- package/dist/ElevenLabsSynthesizer.d.mts.map +1 -0
- package/dist/ElevenLabsSynthesizer.mjs +82 -0
- package/dist/ElevenLabsSynthesizer.mjs.map +1 -0
- package/dist/ElevenLabsSynthesizer.test.d.mts +1 -0
- package/dist/ElevenLabsSynthesizer.test.mjs +32 -0
- package/dist/ElevenLabsSynthesizer.test.mjs.map +1 -0
- package/dist/ElevenLabsTranscriber.d.mts +57 -0
- package/dist/ElevenLabsTranscriber.d.mts.map +1 -0
- package/dist/ElevenLabsTranscriber.mjs +97 -0
- package/dist/ElevenLabsTranscriber.mjs.map +1 -0
- package/dist/ElevenLabsTranscriber.test.d.mts +1 -0
- package/dist/ElevenLabsTranscriber.test.mjs +58 -0
- package/dist/ElevenLabsTranscriber.test.mjs.map +1 -0
- package/dist/chunk-uyGKjUfl.mjs +14 -0
- package/dist/codec.d.mts +43 -0
- package/dist/codec.d.mts.map +1 -0
- package/dist/codec.mjs +120 -0
- package/dist/codec.mjs.map +1 -0
- package/dist/codec.test.d.mts +1 -0
- package/dist/codec.test.mjs +142 -0
- package/dist/codec.test.mjs.map +1 -0
- package/dist/dist-DMsg8YLb.mjs +13782 -0
- package/dist/dist-DMsg8YLb.mjs.map +1 -0
- package/dist/index.d.mts +5 -0
- package/dist/index.mjs +5 -0
- package/dist/magic-string.es-CaWgF-3p.mjs +1013 -0
- package/dist/magic-string.es-CaWgF-3p.mjs.map +1 -0
- package/dist/models.d.mts +37 -0
- package/dist/models.d.mts.map +1 -0
- package/dist/models.mjs +1 -0
- package/dist/realtimeStt.d.mts +67 -0
- package/dist/realtimeStt.d.mts.map +1 -0
- package/dist/realtimeStt.mjs +163 -0
- package/dist/realtimeStt.mjs.map +1 -0
- package/dist/realtimeTts.d.mts +31 -0
- package/dist/realtimeTts.d.mts.map +1 -0
- package/dist/realtimeTts.mjs +77 -0
- package/dist/realtimeTts.mjs.map +1 -0
- package/package.json +64 -0
- package/src/ElevenLabsSynthesizer.test.ts +49 -0
- package/src/ElevenLabsSynthesizer.ts +165 -0
- package/src/ElevenLabsTranscriber.test.ts +63 -0
- package/src/ElevenLabsTranscriber.ts +179 -0
- package/src/codec.test.ts +128 -0
- package/src/codec.ts +185 -0
- package/src/index.ts +4 -0
- package/src/models.ts +46 -0
- package/src/realtimeStt.ts +273 -0
- package/src/realtimeTts.ts +147 -0
package/dist/codec.mjs
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
import { n as __exportAll } from "./chunk-uyGKjUfl.mjs";
|
|
2
|
+
import { Array, Effect, Encoding, Match, Option, Result } from "effect";
|
|
3
|
+
import * as AiError from "@effect-uai/core/AiError";
|
|
4
|
+
//#region src/codec.ts
|
|
5
|
+
var codec_exports = /* @__PURE__ */ __exportAll({
|
|
6
|
+
audioToBlob: () => audioToBlob,
|
|
7
|
+
defaultFileName: () => defaultFileName,
|
|
8
|
+
defaultFormat: () => defaultFormat,
|
|
9
|
+
formatToOutputSlug: () => formatToOutputSlug,
|
|
10
|
+
httpStatusError: () => httpStatusError,
|
|
11
|
+
transportFailure: () => transportFailure,
|
|
12
|
+
wireVoiceSettings: () => wireVoiceSettings
|
|
13
|
+
});
|
|
14
|
+
const wireVoiceSettings = (v) => v === void 0 ? void 0 : {
|
|
15
|
+
...v.stability !== void 0 && { stability: v.stability },
|
|
16
|
+
...v.similarityBoost !== void 0 && { similarity_boost: v.similarityBoost },
|
|
17
|
+
...v.style !== void 0 && { style: v.style },
|
|
18
|
+
...v.useSpeakerBoost !== void 0 && { use_speaker_boost: v.useSpeakerBoost },
|
|
19
|
+
...v.speed !== void 0 && { speed: v.speed }
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* ElevenLabs encodes container + sample rate + bitrate in a single
|
|
23
|
+
* dot-less slug (`mp3_44100_128`, `pcm_16000`, `wav_48000`, `ulaw_8000`,
|
|
24
|
+
* `opus_48000_128`, etc.). This helper inverts the common cases of our
|
|
25
|
+
* `AudioFormat` shape into that slug.
|
|
26
|
+
*
|
|
27
|
+
* Only the formats that map cleanly are produced. Anything we can't
|
|
28
|
+
* encode (mismatched bitrate, unsupported sample rate combination)
|
|
29
|
+
* fails `Unsupported` with the requested format echoed so the user can
|
|
30
|
+
* fix it.
|
|
31
|
+
*/
|
|
32
|
+
const formatToOutputSlug = (format) => Match.value(format).pipe(Match.when({ container: "mp3" }, (f) => Effect.succeed(`mp3_${f.sampleRate}_${f.bitRate ?? 128}`)), Match.when({ container: "opus" }, (f) => Effect.succeed(`opus_${f.sampleRate}_${f.bitRate ?? 128}`)), Match.when({ container: "wav" }, (f) => Effect.succeed(`wav_${f.sampleRate}`)), Match.when({
|
|
33
|
+
container: "raw",
|
|
34
|
+
encoding: "pcm_s16le"
|
|
35
|
+
}, (f) => Effect.succeed(`pcm_${f.sampleRate}`)), Match.when({
|
|
36
|
+
container: "raw",
|
|
37
|
+
encoding: "pcm_mulaw"
|
|
38
|
+
}, (f) => Effect.succeed(`ulaw_${f.sampleRate}`)), Match.when({
|
|
39
|
+
container: "raw",
|
|
40
|
+
encoding: "pcm_alaw"
|
|
41
|
+
}, (f) => Effect.succeed(`alaw_${f.sampleRate}`)), Match.orElse((f) => Effect.fail(new AiError.Unsupported({
|
|
42
|
+
provider: "elevenlabs",
|
|
43
|
+
capability: "outputFormat",
|
|
44
|
+
reason: `Cannot encode ${JSON.stringify(f)} as ElevenLabs output_format. Supported: mp3 + (22050|44100), opus 48000, wav 48000, pcm/ulaw/alaw at any sample rate.`
|
|
45
|
+
}))));
|
|
46
|
+
/** Default output: 128 kbps MP3 at 44.1 kHz. */
|
|
47
|
+
const defaultFormat = {
|
|
48
|
+
container: "mp3",
|
|
49
|
+
encoding: "mp3",
|
|
50
|
+
sampleRate: 44100,
|
|
51
|
+
bitRate: 128
|
|
52
|
+
};
|
|
53
|
+
const urlNotSupported = new AiError.InvalidRequest({
|
|
54
|
+
provider: "elevenlabs",
|
|
55
|
+
param: "audio",
|
|
56
|
+
raw: "ElevenLabs accepts URLs via the `cloud_storage_url` field, not the `file` field. Fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }` to use the inline path."
|
|
57
|
+
});
|
|
58
|
+
const decodeBase64ToBytes = (b64) => Result.match(Encoding.decodeBase64(b64), {
|
|
59
|
+
onSuccess: Effect.succeed,
|
|
60
|
+
onFailure: (cause) => Effect.fail(new AiError.InvalidRequest({
|
|
61
|
+
provider: "elevenlabs",
|
|
62
|
+
param: "audio",
|
|
63
|
+
raw: cause
|
|
64
|
+
}))
|
|
65
|
+
});
|
|
66
|
+
const bytesToBlob = (bytes, mimeType) => new Blob([bytes], { type: mimeType });
|
|
67
|
+
const audioToBlob = Match.type().pipe(Match.tag("bytes", (a) => Effect.succeed(bytesToBlob(a.bytes, a.mimeType))), Match.tag("base64", (a) => decodeBase64ToBytes(a.base64).pipe(Effect.map((bytes) => bytesToBlob(bytes, a.mimeType)))), Match.tag("url", () => Effect.fail(urlNotSupported)), Match.exhaustive);
|
|
68
|
+
const EXTENSION_BY_MIME = [
|
|
69
|
+
["mpeg", "mp3"],
|
|
70
|
+
["mp3", "mp3"],
|
|
71
|
+
["wav", "wav"],
|
|
72
|
+
["ogg", "ogg"],
|
|
73
|
+
["opus", "ogg"],
|
|
74
|
+
["flac", "flac"],
|
|
75
|
+
["aac", "aac"],
|
|
76
|
+
["mp4", "m4a"],
|
|
77
|
+
["m4a", "m4a"],
|
|
78
|
+
["webm", "webm"]
|
|
79
|
+
];
|
|
80
|
+
const defaultFileName = (mimeType) => Array.findFirst(EXTENSION_BY_MIME, ([pattern]) => mimeType.includes(pattern)).pipe(Option.match({
|
|
81
|
+
onNone: () => "audio",
|
|
82
|
+
onSome: ([, ext]) => `audio.${ext}`
|
|
83
|
+
}));
|
|
84
|
+
const httpStatusError = (status, body) => Match.value(status).pipe(Match.when(429, () => new AiError.RateLimited({
|
|
85
|
+
provider: "elevenlabs",
|
|
86
|
+
raw: body
|
|
87
|
+
})), Match.whenOr(408, 504, () => new AiError.Timeout({
|
|
88
|
+
provider: "elevenlabs",
|
|
89
|
+
raw: body
|
|
90
|
+
})), Match.when(401, () => new AiError.AuthFailed({
|
|
91
|
+
provider: "elevenlabs",
|
|
92
|
+
subtype: "auth",
|
|
93
|
+
raw: body
|
|
94
|
+
})), Match.when(403, () => new AiError.AuthFailed({
|
|
95
|
+
provider: "elevenlabs",
|
|
96
|
+
subtype: "permission",
|
|
97
|
+
raw: body
|
|
98
|
+
})), Match.when(402, () => new AiError.AuthFailed({
|
|
99
|
+
provider: "elevenlabs",
|
|
100
|
+
subtype: "billing",
|
|
101
|
+
raw: body
|
|
102
|
+
})), Match.when(413, () => new AiError.ContextLengthExceeded({
|
|
103
|
+
provider: "elevenlabs",
|
|
104
|
+
raw: body
|
|
105
|
+
})), Match.when((n) => n >= 500, (n) => new AiError.Unavailable({
|
|
106
|
+
provider: "elevenlabs",
|
|
107
|
+
status: n,
|
|
108
|
+
raw: body
|
|
109
|
+
})), Match.orElse(() => new AiError.InvalidRequest({
|
|
110
|
+
provider: "elevenlabs",
|
|
111
|
+
raw: body
|
|
112
|
+
})));
|
|
113
|
+
const transportFailure = (cause) => new AiError.Unavailable({
|
|
114
|
+
provider: "elevenlabs",
|
|
115
|
+
raw: cause
|
|
116
|
+
});
|
|
117
|
+
//#endregion
|
|
118
|
+
export { audioToBlob, defaultFileName, defaultFormat, formatToOutputSlug, httpStatusError, codec_exports as t, transportFailure, wireVoiceSettings };
|
|
119
|
+
|
|
120
|
+
//# sourceMappingURL=codec.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.mjs","names":["Arr"],"sources":["../src/codec.ts"],"sourcesContent":["import { Array as Arr, Effect, Encoding, Match, Option, Result } from \"effect\"\nimport * as AiError from \"@effect-uai/core/AiError\"\nimport type { AudioFormat, AudioSource } from \"@effect-uai/core/Audio\"\n\n// ---------------------------------------------------------------------------\n// Shared TTS voice-settings shape (sync + streaming use the same fields)\n// ---------------------------------------------------------------------------\n\nexport type VoiceSettings = {\n readonly stability?: number\n readonly similarityBoost?: number\n readonly style?: number\n readonly useSpeakerBoost?: boolean\n readonly speed?: number\n}\n\nexport const wireVoiceSettings = (v: VoiceSettings | undefined) =>\n v === undefined\n ? undefined\n : {\n ...(v.stability !== undefined && { stability: v.stability }),\n ...(v.similarityBoost !== undefined && { similarity_boost: v.similarityBoost }),\n ...(v.style !== undefined && { style: v.style }),\n ...(v.useSpeakerBoost !== undefined && { use_speaker_boost: v.useSpeakerBoost }),\n ...(v.speed !== undefined && { speed: v.speed }),\n }\n\n// ---------------------------------------------------------------------------\n// AudioFormat → ElevenLabs `output_format` query value\n// ---------------------------------------------------------------------------\n\n/**\n * ElevenLabs encodes container + sample rate + bitrate in a single\n * dot-less slug (`mp3_44100_128`, `pcm_16000`, `wav_48000`, `ulaw_8000`,\n * `opus_48000_128`, etc.). This helper inverts the common cases of our\n * `AudioFormat` shape into that slug.\n *\n * Only the formats that map cleanly are produced. Anything we can't\n * encode (mismatched bitrate, unsupported sample rate combination)\n * fails `Unsupported` with the requested format echoed so the user can\n * fix it.\n */\nexport const formatToOutputSlug: (format: AudioFormat) => Effect.Effect<string, AiError.AiError> = (\n format,\n) =>\n Match.value(format).pipe(\n Match.when({ container: \"mp3\" }, (f) =>\n Effect.succeed(`mp3_${f.sampleRate}_${f.bitRate ?? 128}`),\n ),\n Match.when({ container: \"opus\" }, (f) =>\n Effect.succeed(`opus_${f.sampleRate}_${f.bitRate ?? 128}`),\n ),\n Match.when({ container: \"wav\" }, (f) => Effect.succeed(`wav_${f.sampleRate}`)),\n Match.when({ container: \"raw\", encoding: \"pcm_s16le\" }, (f) =>\n Effect.succeed(`pcm_${f.sampleRate}`),\n ),\n Match.when({ container: \"raw\", encoding: \"pcm_mulaw\" }, (f) =>\n Effect.succeed(`ulaw_${f.sampleRate}`),\n ),\n Match.when({ container: \"raw\", encoding: \"pcm_alaw\" }, (f) =>\n Effect.succeed(`alaw_${f.sampleRate}`),\n ),\n Match.orElse((f) =>\n Effect.fail(\n new AiError.Unsupported({\n provider: \"elevenlabs\",\n capability: \"outputFormat\",\n reason: `Cannot encode ${JSON.stringify(f)} as ElevenLabs output_format. Supported: mp3 + (22050|44100), opus 48000, wav 48000, pcm/ulaw/alaw at any sample rate.`,\n }),\n ),\n ),\n )\n\n/** Default output: 128 kbps MP3 at 44.1 kHz. */\nexport const defaultFormat: AudioFormat = {\n container: \"mp3\",\n encoding: \"mp3\",\n sampleRate: 44100,\n bitRate: 128,\n}\n\n// ---------------------------------------------------------------------------\n// AudioSource → Blob (STT multipart upload)\n// ---------------------------------------------------------------------------\n\nconst urlNotSupported: AiError.AiError = new AiError.InvalidRequest({\n provider: \"elevenlabs\",\n param: \"audio\",\n raw: 'ElevenLabs accepts URLs via the `cloud_storage_url` field, not the `file` field. Fetch the URL yourself and pass `{ _tag: \"bytes\", bytes, mimeType }` to use the inline path.',\n})\n\nconst decodeBase64ToBytes = (b64: string): Effect.Effect<Uint8Array, AiError.AiError> =>\n Result.match(Encoding.decodeBase64(b64), {\n onSuccess: Effect.succeed,\n onFailure: (cause) =>\n Effect.fail(\n new AiError.InvalidRequest({ provider: \"elevenlabs\", param: \"audio\", raw: cause }),\n ),\n })\n\nconst bytesToBlob = (bytes: Uint8Array, mimeType: string): Blob =>\n new Blob([bytes as Uint8Array<ArrayBuffer>], { type: mimeType })\n\nexport const audioToBlob: (audio: AudioSource) => Effect.Effect<Blob, AiError.AiError> =\n Match.type<AudioSource>().pipe(\n Match.tag(\"bytes\", (a) => Effect.succeed(bytesToBlob(a.bytes, a.mimeType))),\n Match.tag(\"base64\", (a) =>\n decodeBase64ToBytes(a.base64).pipe(Effect.map((bytes) => bytesToBlob(bytes, a.mimeType))),\n ),\n Match.tag(\"url\", () => Effect.fail(urlNotSupported)),\n Match.exhaustive,\n )\n\n// ---------------------------------------------------------------------------\n// MIME → file extension (for the multipart `filename` field)\n// ---------------------------------------------------------------------------\n\nconst EXTENSION_BY_MIME: ReadonlyArray<readonly [string, string]> = [\n [\"mpeg\", \"mp3\"],\n [\"mp3\", \"mp3\"],\n [\"wav\", \"wav\"],\n [\"ogg\", \"ogg\"],\n [\"opus\", \"ogg\"],\n [\"flac\", \"flac\"],\n [\"aac\", \"aac\"],\n [\"mp4\", \"m4a\"],\n [\"m4a\", \"m4a\"],\n [\"webm\", \"webm\"],\n]\n\nexport const defaultFileName = (mimeType: string): string =>\n Arr.findFirst(EXTENSION_BY_MIME, ([pattern]) => mimeType.includes(pattern)).pipe(\n Option.match({\n onNone: () => \"audio\",\n onSome: ([, ext]) => `audio.${ext}`,\n }),\n )\n\n// ---------------------------------------------------------------------------\n// HTTP errors\n// ---------------------------------------------------------------------------\n\nexport const httpStatusError: (status: number, body: string) => AiError.AiError = (status, body) =>\n Match.value(status).pipe(\n Match.when(\n 429,\n (): AiError.AiError => new AiError.RateLimited({ provider: \"elevenlabs\", raw: body }),\n ),\n Match.whenOr(\n 408,\n 504,\n (): AiError.AiError => new AiError.Timeout({ provider: \"elevenlabs\", raw: body }),\n ),\n Match.when(\n 401,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"elevenlabs\", subtype: \"auth\", raw: body }),\n ),\n Match.when(\n 403,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"elevenlabs\", subtype: \"permission\", raw: body }),\n ),\n Match.when(\n 402,\n (): AiError.AiError =>\n new AiError.AuthFailed({ provider: \"elevenlabs\", subtype: \"billing\", raw: body }),\n ),\n Match.when(\n 413,\n (): AiError.AiError =>\n new AiError.ContextLengthExceeded({ provider: \"elevenlabs\", raw: body }),\n ),\n Match.when(\n (n) => n >= 500,\n (n): AiError.AiError =>\n new AiError.Unavailable({ provider: \"elevenlabs\", status: n, raw: body }),\n ),\n Match.orElse(\n (): AiError.AiError => new AiError.InvalidRequest({ provider: \"elevenlabs\", raw: body }),\n ),\n )\n\nexport const transportFailure = (cause: unknown): AiError.AiError =>\n new AiError.Unavailable({ provider: \"elevenlabs\", raw: cause })\n"],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { i as it, n as globalExpect, r as describe } from "./dist-DMsg8YLb.mjs";
|
|
2
|
+
import { audioToBlob, defaultFileName, formatToOutputSlug, httpStatusError } from "./codec.mjs";
|
|
3
|
+
import { Effect, Encoding } from "effect";
|
|
4
|
+
//#region src/codec.test.ts
|
|
5
|
+
describe("formatToOutputSlug", () => {
|
|
6
|
+
it("encodes mp3 with default bitrate when omitted", async () => {
|
|
7
|
+
globalExpect(await Effect.runPromise(formatToOutputSlug({
|
|
8
|
+
container: "mp3",
|
|
9
|
+
encoding: "mp3",
|
|
10
|
+
sampleRate: 44100
|
|
11
|
+
}))).toBe("mp3_44100_128");
|
|
12
|
+
});
|
|
13
|
+
it("honours an explicit mp3 bitrate", async () => {
|
|
14
|
+
globalExpect(await Effect.runPromise(formatToOutputSlug({
|
|
15
|
+
container: "mp3",
|
|
16
|
+
encoding: "mp3",
|
|
17
|
+
sampleRate: 22050,
|
|
18
|
+
bitRate: 32
|
|
19
|
+
}))).toBe("mp3_22050_32");
|
|
20
|
+
});
|
|
21
|
+
it("encodes raw pcm_s16le as `pcm_<sampleRate>`", async () => {
|
|
22
|
+
globalExpect(await Effect.runPromise(formatToOutputSlug({
|
|
23
|
+
container: "raw",
|
|
24
|
+
encoding: "pcm_s16le",
|
|
25
|
+
sampleRate: 16e3
|
|
26
|
+
}))).toBe("pcm_16000");
|
|
27
|
+
});
|
|
28
|
+
it("encodes mu-law as `ulaw_<sampleRate>`", async () => {
|
|
29
|
+
globalExpect(await Effect.runPromise(formatToOutputSlug({
|
|
30
|
+
container: "raw",
|
|
31
|
+
encoding: "pcm_mulaw",
|
|
32
|
+
sampleRate: 8e3
|
|
33
|
+
}))).toBe("ulaw_8000");
|
|
34
|
+
});
|
|
35
|
+
it("encodes opus with default bitrate", async () => {
|
|
36
|
+
globalExpect(await Effect.runPromise(formatToOutputSlug({
|
|
37
|
+
container: "opus",
|
|
38
|
+
encoding: "opus",
|
|
39
|
+
sampleRate: 48e3
|
|
40
|
+
}))).toBe("opus_48000_128");
|
|
41
|
+
});
|
|
42
|
+
it("encodes wav as `wav_<sampleRate>`", async () => {
|
|
43
|
+
globalExpect(await Effect.runPromise(formatToOutputSlug({
|
|
44
|
+
container: "wav",
|
|
45
|
+
encoding: "pcm_s16le",
|
|
46
|
+
sampleRate: 48e3
|
|
47
|
+
}))).toBe("wav_48000");
|
|
48
|
+
});
|
|
49
|
+
it("fails Unsupported for flac / aac / webm / ogg", async () => {
|
|
50
|
+
const exit = await Effect.runPromiseExit(formatToOutputSlug({
|
|
51
|
+
container: "flac",
|
|
52
|
+
encoding: "flac",
|
|
53
|
+
sampleRate: 44100
|
|
54
|
+
}));
|
|
55
|
+
globalExpect(exit._tag).toBe("Failure");
|
|
56
|
+
if (exit._tag === "Failure") {
|
|
57
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("Unsupported");
|
|
58
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("outputFormat");
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
describe("audioToBlob", () => {
|
|
63
|
+
it("wraps bytes into a Blob with the right MIME type", async () => {
|
|
64
|
+
const source = {
|
|
65
|
+
_tag: "bytes",
|
|
66
|
+
bytes: new Uint8Array([
|
|
67
|
+
1,
|
|
68
|
+
2,
|
|
69
|
+
3,
|
|
70
|
+
4
|
|
71
|
+
]),
|
|
72
|
+
mimeType: "audio/wav"
|
|
73
|
+
};
|
|
74
|
+
const blob = await Effect.runPromise(audioToBlob(source));
|
|
75
|
+
globalExpect(blob.type).toBe("audio/wav");
|
|
76
|
+
globalExpect(blob.size).toBe(4);
|
|
77
|
+
});
|
|
78
|
+
it("decodes a base64 source into bytes", async () => {
|
|
79
|
+
const original = new Uint8Array([
|
|
80
|
+
222,
|
|
81
|
+
173,
|
|
82
|
+
190,
|
|
83
|
+
239
|
|
84
|
+
]);
|
|
85
|
+
const source = {
|
|
86
|
+
_tag: "base64",
|
|
87
|
+
base64: Encoding.encodeBase64(original),
|
|
88
|
+
mimeType: "audio/mpeg"
|
|
89
|
+
};
|
|
90
|
+
const blob = await Effect.runPromise(audioToBlob(source));
|
|
91
|
+
globalExpect(blob.type).toBe("audio/mpeg");
|
|
92
|
+
globalExpect(blob.size).toBe(4);
|
|
93
|
+
});
|
|
94
|
+
it("fails InvalidRequest for url sources", async () => {
|
|
95
|
+
const exit = await Effect.runPromiseExit(audioToBlob({
|
|
96
|
+
_tag: "url",
|
|
97
|
+
url: "https://x/y.mp3",
|
|
98
|
+
mimeType: "audio/mpeg"
|
|
99
|
+
}));
|
|
100
|
+
globalExpect(exit._tag).toBe("Failure");
|
|
101
|
+
if (exit._tag === "Failure") {
|
|
102
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("InvalidRequest");
|
|
103
|
+
globalExpect(JSON.stringify(exit.cause)).toContain("cloud_storage_url");
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
describe("defaultFileName", () => {
|
|
108
|
+
it("maps audio/mpeg → audio.mp3", () => {
|
|
109
|
+
globalExpect(defaultFileName("audio/mpeg")).toBe("audio.mp3");
|
|
110
|
+
});
|
|
111
|
+
it("maps audio/wav → audio.wav", () => {
|
|
112
|
+
globalExpect(defaultFileName("audio/wav")).toBe("audio.wav");
|
|
113
|
+
});
|
|
114
|
+
it("falls back to audio (no extension) for unknown MIME", () => {
|
|
115
|
+
globalExpect(defaultFileName("application/octet-stream")).toBe("audio");
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
describe("httpStatusError", () => {
|
|
119
|
+
const tags = (status) => JSON.parse(JSON.stringify(httpStatusError(status, "body")))._tag;
|
|
120
|
+
it("maps 429 to RateLimited", () => globalExpect(tags(429)).toBe("RateLimited"));
|
|
121
|
+
it("maps 408/504 to Timeout", () => {
|
|
122
|
+
globalExpect(tags(408)).toBe("Timeout");
|
|
123
|
+
globalExpect(tags(504)).toBe("Timeout");
|
|
124
|
+
});
|
|
125
|
+
it("maps 401/402/403 to AuthFailed", () => {
|
|
126
|
+
globalExpect(tags(401)).toBe("AuthFailed");
|
|
127
|
+
globalExpect(tags(402)).toBe("AuthFailed");
|
|
128
|
+
globalExpect(tags(403)).toBe("AuthFailed");
|
|
129
|
+
});
|
|
130
|
+
it("maps 5xx to Unavailable", () => {
|
|
131
|
+
globalExpect(tags(500)).toBe("Unavailable");
|
|
132
|
+
globalExpect(tags(503)).toBe("Unavailable");
|
|
133
|
+
});
|
|
134
|
+
it("maps other 4xx to InvalidRequest", () => {
|
|
135
|
+
globalExpect(tags(400)).toBe("InvalidRequest");
|
|
136
|
+
globalExpect(tags(404)).toBe("InvalidRequest");
|
|
137
|
+
});
|
|
138
|
+
});
|
|
139
|
+
//#endregion
|
|
140
|
+
export {};
|
|
141
|
+
|
|
142
|
+
//# sourceMappingURL=codec.test.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"codec.test.mjs","names":["expect"],"sources":["../src/codec.test.ts"],"sourcesContent":["import { Effect, Encoding } from \"effect\"\nimport { describe, expect, it } from \"vitest\"\nimport type { AudioSource } from \"@effect-uai/core/Audio\"\nimport { audioToBlob, defaultFileName, formatToOutputSlug, httpStatusError } from \"./codec.js\"\n\ndescribe(\"formatToOutputSlug\", () => {\n it(\"encodes mp3 with default bitrate when omitted\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"mp3\", encoding: \"mp3\", sampleRate: 44100 }),\n )\n expect(slug).toBe(\"mp3_44100_128\")\n })\n\n it(\"honours an explicit mp3 bitrate\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"mp3\", encoding: \"mp3\", sampleRate: 22050, bitRate: 32 }),\n )\n expect(slug).toBe(\"mp3_22050_32\")\n })\n\n it(\"encodes raw pcm_s16le as `pcm_<sampleRate>`\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"raw\", encoding: \"pcm_s16le\", sampleRate: 16000 }),\n )\n expect(slug).toBe(\"pcm_16000\")\n })\n\n it(\"encodes mu-law as `ulaw_<sampleRate>`\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"raw\", encoding: \"pcm_mulaw\", sampleRate: 8000 }),\n )\n expect(slug).toBe(\"ulaw_8000\")\n })\n\n it(\"encodes opus with default bitrate\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"opus\", encoding: \"opus\", sampleRate: 48000 }),\n )\n expect(slug).toBe(\"opus_48000_128\")\n })\n\n it(\"encodes wav as `wav_<sampleRate>`\", async () => {\n const slug = await Effect.runPromise(\n formatToOutputSlug({ container: \"wav\", encoding: \"pcm_s16le\", sampleRate: 48000 }),\n )\n expect(slug).toBe(\"wav_48000\")\n })\n\n it(\"fails Unsupported for flac / aac / webm / ogg\", async () => {\n const exit = await Effect.runPromiseExit(\n formatToOutputSlug({ container: \"flac\", encoding: \"flac\", sampleRate: 44100 }),\n )\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"Unsupported\")\n expect(JSON.stringify(exit.cause)).toContain(\"outputFormat\")\n }\n })\n})\n\ndescribe(\"audioToBlob\", () => {\n it(\"wraps bytes into a Blob with the right MIME type\", async () => {\n const source: AudioSource = {\n _tag: \"bytes\",\n bytes: new Uint8Array([1, 2, 3, 4]),\n mimeType: \"audio/wav\",\n }\n const blob = await Effect.runPromise(audioToBlob(source))\n expect(blob.type).toBe(\"audio/wav\")\n expect(blob.size).toBe(4)\n })\n\n it(\"decodes a base64 source into bytes\", async () => {\n const original = new Uint8Array([0xde, 0xad, 0xbe, 0xef])\n const source: AudioSource = {\n _tag: \"base64\",\n base64: Encoding.encodeBase64(original),\n mimeType: \"audio/mpeg\",\n }\n const blob = await Effect.runPromise(audioToBlob(source))\n expect(blob.type).toBe(\"audio/mpeg\")\n expect(blob.size).toBe(4)\n })\n\n it(\"fails InvalidRequest for url sources\", async () => {\n const source: AudioSource = { _tag: \"url\", url: \"https://x/y.mp3\", mimeType: \"audio/mpeg\" }\n const exit = await Effect.runPromiseExit(audioToBlob(source))\n expect(exit._tag).toBe(\"Failure\")\n if (exit._tag === \"Failure\") {\n expect(JSON.stringify(exit.cause)).toContain(\"InvalidRequest\")\n expect(JSON.stringify(exit.cause)).toContain(\"cloud_storage_url\")\n }\n })\n})\n\ndescribe(\"defaultFileName\", () => {\n it(\"maps audio/mpeg → audio.mp3\", () => {\n expect(defaultFileName(\"audio/mpeg\")).toBe(\"audio.mp3\")\n })\n it(\"maps audio/wav → audio.wav\", () => {\n expect(defaultFileName(\"audio/wav\")).toBe(\"audio.wav\")\n })\n it(\"falls back to audio (no extension) for unknown MIME\", () => {\n expect(defaultFileName(\"application/octet-stream\")).toBe(\"audio\")\n })\n})\n\ndescribe(\"httpStatusError\", () => {\n const tags = (status: number) => JSON.parse(JSON.stringify(httpStatusError(status, \"body\")))._tag\n it(\"maps 429 to RateLimited\", () => expect(tags(429)).toBe(\"RateLimited\"))\n it(\"maps 408/504 to Timeout\", () => {\n expect(tags(408)).toBe(\"Timeout\")\n expect(tags(504)).toBe(\"Timeout\")\n })\n it(\"maps 401/402/403 to AuthFailed\", () => {\n expect(tags(401)).toBe(\"AuthFailed\")\n expect(tags(402)).toBe(\"AuthFailed\")\n expect(tags(403)).toBe(\"AuthFailed\")\n })\n it(\"maps 5xx to Unavailable\", () => {\n expect(tags(500)).toBe(\"Unavailable\")\n expect(tags(503)).toBe(\"Unavailable\")\n })\n it(\"maps other 4xx to InvalidRequest\", () => {\n expect(tags(400)).toBe(\"InvalidRequest\")\n expect(tags(404)).toBe(\"InvalidRequest\")\n })\n})\n"],"mappings":"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"}
|