@clipwright/core 0.19.0 → 0.20.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/CHANGELOG.md +14 -0
- package/dist/__fixtures__/queue-promises.d.ts +5 -84
- package/dist/__fixtures__/queue-promises.js +7 -84
- package/dist/__fixtures__/queue-promises.js.map +1 -1
- package/dist/account-actors.d.ts +25 -25
- package/dist/account-actors.js +25 -25
- package/dist/account-actors.js.map +1 -1
- package/dist/account.d.ts +4 -10
- package/dist/account.js +6 -12
- package/dist/account.js.map +1 -1
- package/dist/actor-bounds.d.ts +4 -4
- package/dist/actor-bounds.js +4 -4
- package/dist/actor-bounds.js.map +1 -1
- package/dist/actor-qualities.d.ts +3 -3
- package/dist/actor-qualities.js +3 -3
- package/dist/actor-qualities.js.map +1 -1
- package/dist/actors.d.ts +8 -8
- package/dist/actors.js +9 -9
- package/dist/actors.js.map +1 -1
- package/dist/api-failure.d.ts +28 -129
- package/dist/api-failure.js +52 -225
- package/dist/api-failure.js.map +1 -1
- package/dist/aspect.d.ts +26 -96
- package/dist/aspect.js +32 -108
- package/dist/aspect.js.map +1 -1
- package/dist/contract-dispositions.d.ts +42 -167
- package/dist/contract-dispositions.js +54 -184
- package/dist/contract-dispositions.js.map +1 -1
- package/dist/endpoints.d.ts +5 -5
- package/dist/endpoints.js +3 -3
- package/dist/endpoints.js.map +1 -1
- package/dist/errors.d.ts +33 -101
- package/dist/errors.js +42 -108
- package/dist/errors.js.map +1 -1
- package/dist/faceless-cut-style.d.ts +115 -0
- package/dist/faceless-cut-style.js +109 -0
- package/dist/faceless-cut-style.js.map +1 -0
- package/dist/faceless-image-qualification.d.ts +97 -0
- package/dist/faceless-image-qualification.js +145 -0
- package/dist/faceless-image-qualification.js.map +1 -0
- package/dist/faceless-short-61.d.ts +83 -0
- package/dist/faceless-short-61.js +152 -0
- package/dist/faceless-short-61.js.map +1 -0
- package/dist/faceless-storyboard-timing.d.ts +40 -0
- package/dist/faceless-storyboard-timing.js +97 -0
- package/dist/faceless-storyboard-timing.js.map +1 -0
- package/dist/faceless-storyboard-v2-plan.d.ts +3 -0
- package/dist/faceless-storyboard-v2-plan.js +112 -0
- package/dist/faceless-storyboard-v2-plan.js.map +1 -0
- package/dist/faceless-storyboard-v2-timing.d.ts +52 -0
- package/dist/faceless-storyboard-v2-timing.js +136 -0
- package/dist/faceless-storyboard-v2-timing.js.map +1 -0
- package/dist/faceless-storyboard-v2.d.ts +44 -0
- package/dist/faceless-storyboard-v2.js +43 -0
- package/dist/faceless-storyboard-v2.js.map +1 -0
- package/dist/faceless-storyboard.d.ts +55 -0
- package/dist/faceless-storyboard.js +105 -0
- package/dist/faceless-storyboard.js.map +1 -0
- package/dist/idempotency.d.ts +8 -44
- package/dist/idempotency.js +10 -44
- package/dist/idempotency.js.map +1 -1
- package/dist/index.js +21 -25
- package/dist/index.js.map +1 -1
- package/dist/long-form-pricing.d.ts +4 -4
- package/dist/long-form-pricing.js +4 -4
- package/dist/long-form-pricing.js.map +1 -1
- package/dist/long-form-profile.d.ts +2 -2
- package/dist/long-form-profile.js +2 -2
- package/dist/long-form-profile.js.map +1 -1
- package/dist/pacing.d.ts +1 -1
- package/dist/pacing.js +1 -1
- package/dist/pacing.js.map +1 -1
- package/dist/public-endpoints.d.ts +9 -11
- package/dist/public-endpoints.js +11 -13
- package/dist/public-endpoints.js.map +1 -1
- package/dist/rate-limit.d.ts +23 -130
- package/dist/rate-limit.js +27 -132
- package/dist/rate-limit.js.map +1 -1
- package/dist/render-backend.d.ts +30 -145
- package/dist/render-backend.js +4 -27
- package/dist/render-backend.js.map +1 -1
- package/dist/run-preflight.d.ts +4 -4
- package/dist/run-preflight.js +10 -10
- package/dist/run-preflight.js.map +1 -1
- package/dist/run-static-warnings.d.ts +8 -8
- package/dist/run-static-warnings.js +24 -24
- package/dist/run-static-warnings.js.map +1 -1
- package/dist/runs.d.ts +18 -55
- package/dist/runs.js +26 -94
- package/dist/runs.js.map +1 -1
- package/dist/script-integrity.d.ts +6 -7
- package/dist/script-integrity.js +7 -8
- package/dist/script-integrity.js.map +1 -1
- package/dist/semantic-inserts.d.ts +4 -4
- package/dist/semantic-inserts.js +6 -6
- package/dist/semantic-inserts.js.map +1 -1
- package/dist/skills.d.ts +33 -107
- package/dist/skills.js +69 -160
- package/dist/skills.js.map +1 -1
- package/dist/tts-backend.d.ts +13 -42
- package/dist/tts-backend.js +2 -10
- package/dist/tts-backend.js.map +1 -1
- package/dist/uploads.d.ts +4 -4
- package/dist/uploads.js +4 -4
- package/dist/uploads.js.map +1 -1
- package/dist/vendor-response.d.ts +17 -80
- package/dist/vendor-response.js +19 -67
- package/dist/vendor-response.js.map +1 -1
- package/dist/voice-admission.d.ts +5 -5
- package/dist/voice-admission.js +6 -6
- package/dist/voice-admission.js.map +1 -1
- package/dist/voice-catalog-data.d.ts +2 -2
- package/dist/voice-catalog-data.js +2 -2
- package/dist/voice-catalog-data.js.map +1 -1
- package/dist/voice-catalog.d.ts +5 -5
- package/dist/voice-catalog.js +6 -6
- package/dist/voice-catalog.js.map +1 -1
- package/dist/voice-listing.d.ts +4 -4
- package/dist/voice-listing.js +6 -6
- package/dist/voice-listing.js.map +1 -1
- package/dist/voices.d.ts +40 -82
- package/dist/voices.js +56 -99
- package/dist/voices.js.map +1 -1
- package/package.json +1 -1
package/dist/voice-listing.js
CHANGED
|
@@ -2,12 +2,12 @@ import { z } from "zod";
|
|
|
2
2
|
import { CATALOG_LANGUAGES, normalizeVoiceLabel, VOICES_QUERY_FIELDS, voicesQueryShape, } from "./voice-catalog.js";
|
|
3
3
|
import { TTS_MODEL_LANGUAGES, VOICE_CATALOG } from "./voice-catalog-data.js";
|
|
4
4
|
import { resolveTtsModel, TTS_MODEL_CHAR_CAP, TTS_MODELS, VOICE_PRESETS, } from "./voices.js";
|
|
5
|
-
//
|
|
6
|
-
//
|
|
5
|
+
// A subpath, not the barrel: the list reads the catalog, which SDK clients do not need.
|
|
6
|
+
// A weekly worker probe checks the same data against the vendor.
|
|
7
7
|
export { TTS_MODEL_LANGUAGES, VOICE_CATALOG } from "./voice-catalog-data.js";
|
|
8
8
|
const isLive = (voice) => voice.retired_at === undefined;
|
|
9
|
-
/**
|
|
10
|
-
/**
|
|
9
|
+
/** Models for which the vendor verifies ANY library voice at all. Derived from the */
|
|
10
|
+
/** catalog, not listed. */
|
|
11
11
|
export function vendorVerifiedModels(voices = VOICE_CATALOG.voices) {
|
|
12
12
|
return TTS_MODELS.filter((model) => voices.some((voice) => isLive(voice) && voice.verified_models.includes(model)));
|
|
13
13
|
}
|
|
@@ -15,7 +15,7 @@ function labelValues(voices, raw) {
|
|
|
15
15
|
const values = voices.filter(isLive).map((voice) => normalizeVoiceLabel(raw(voice)));
|
|
16
16
|
return [...new Set(values.filter((value) => value !== undefined))].sort();
|
|
17
17
|
}
|
|
18
|
-
/**
|
|
18
|
+
/** The server's `/v1/voices` query: an unfamiliar label is refused with the allowed list, not an empty result. */
|
|
19
19
|
export function serverVoicesQueryFor(voices = VOICE_CATALOG.voices) {
|
|
20
20
|
return z.object({
|
|
21
21
|
...voicesQueryShape,
|
|
@@ -25,7 +25,7 @@ export function serverVoicesQueryFor(voices = VOICE_CATALOG.voices) {
|
|
|
25
25
|
}
|
|
26
26
|
export const serverVoicesQuery = serverVoicesQueryFor();
|
|
27
27
|
const presentText = (raw) => raw?.trim() || undefined;
|
|
28
|
-
/**
|
|
28
|
+
/** Presets, then live catalog voices by language and rank; a field the voice lacks matches nothing. */
|
|
29
29
|
export function listVoices(query, catalog = VOICE_CATALOG, modelLanguages = TTS_MODEL_LANGUAGES) {
|
|
30
30
|
const languagesOf = (id) => modelLanguages.models.find((model) => model.id === id)?.languages ?? [];
|
|
31
31
|
const supportedModels = (language) => TTS_MODELS.filter((id) => languagesOf(id).includes(language));
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"voice-listing.js","sourceRoot":"","sources":["../src/voice-listing.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EACL,iBAAiB,EACjB,mBAAmB,EACnB,mBAAmB,EACnB,gBAAgB,GAKjB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,mBAAmB,EAAE,aAAa,EAAE,MAAM,yBAAyB,CAAC;AAC7E,OAAO,EACL,eAAe,EACf,kBAAkB,EAClB,UAAU,EACV,aAAa,GAEd,MAAM,aAAa,CAAC;AAErB,
|
|
1
|
+
{"version":3,"file":"voice-listing.js","sourceRoot":"","sources":["../src/voice-listing.ts"],"names":[],"mappings":"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"}
|
package/dist/voices.d.ts
CHANGED
|
@@ -1,41 +1,40 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { Gender } from "./default-actor.js";
|
|
3
|
-
/**
|
|
4
|
-
/**
|
|
3
|
+
/** ElevenLabs speech models the contract lets callers name. `multilingual_v2` was dropped: */
|
|
4
|
+
/** it honored none of the stress-marking methods we measured. */
|
|
5
5
|
export declare const TTS_MODELS: readonly ["eleven_v3", "eleven_flash_v2_5", "eleven_turbo_v2_5"];
|
|
6
6
|
export type TtsModelId = (typeof TTS_MODELS)[number];
|
|
7
7
|
export declare const DEFAULT_TTS_MODEL: TtsModelId;
|
|
8
|
-
/**
|
|
8
|
+
/** The v3 cap is the vendor's. For 2.5 the vendor allows 40,000; 10,000 is our run-length cap. */
|
|
9
9
|
export declare const TTS_MODEL_CHAR_CAP: Readonly<Record<TtsModelId, number>>;
|
|
10
|
-
/**
|
|
10
|
+
/** Only v3 reads the U+0301 stress mark; flash misreads the same mark three times as often. */
|
|
11
11
|
export declare const STRESS_MARK: string;
|
|
12
12
|
export declare const STRESS_MARK_MODELS: ReadonlySet<TtsModelId>;
|
|
13
|
-
/**
|
|
13
|
+
/** Turns an inner capital vowel into a stress mark. A word-initial capital stays a capital; */
|
|
14
|
+
/** an existing mark is not doubled. */
|
|
14
15
|
export declare function stressMarksFromCapitals(text: string): string;
|
|
15
|
-
/**
|
|
16
|
+
/** The text sent to the vendor: stress marking only for a model that reads it. */
|
|
16
17
|
export declare function speechTextFor(text: string, modelId: TtsModelId): string;
|
|
17
18
|
export declare const MAX_SCRIPT_CHARS: number;
|
|
18
19
|
export declare const SCRIPT_LENGTH_DESCRIPTION: string;
|
|
19
20
|
export declare const STRESS_MARKING_DESCRIPTION: string;
|
|
20
|
-
/**
|
|
21
|
+
/** Length is measured on the text sent to the vendor, stress marks included. */
|
|
21
22
|
export declare function ttsScriptLimitError(text: string, modelId: string): string | undefined;
|
|
22
|
-
/**
|
|
23
|
-
/**
|
|
23
|
+
/** Voice preset catalog: OUR names and metadata, WITHOUT vendor ids. Names feed `tools/list` */
|
|
24
|
+
/** and SDK types; ids live only in the worker adapter. */
|
|
24
25
|
export interface VoicePreset {
|
|
25
|
-
/**
|
|
26
|
+
/** Voice language as a short BCP-47-like code (ru/en). */
|
|
26
27
|
language: string;
|
|
27
|
-
/**
|
|
28
|
-
/**
|
|
28
|
+
/** Voice gender. Omitted only when WE do not know it: vendor silence about our own */
|
|
29
|
+
/** clone is not ignorance. */
|
|
29
30
|
gender?: Gender;
|
|
30
|
-
/**
|
|
31
|
+
/** Human-readable description for list_voices. */
|
|
31
32
|
description: string;
|
|
32
|
-
/**
|
|
33
|
+
/** The speech model the voice is tuned for; clients may override with `tts_model`. */
|
|
33
34
|
model: TtsModelId;
|
|
34
35
|
}
|
|
35
|
-
/**
|
|
36
|
-
|
|
37
|
-
* презентации; дефолт задаётся отдельно через `DEFAULT_VOICE_PRESET`.
|
|
38
|
-
*/
|
|
36
|
+
/** The starter set of presets. Order matters only for presentation; the default is set */
|
|
37
|
+
/** separately by `DEFAULT_VOICE_PRESET`. */
|
|
39
38
|
export declare const VOICE_PRESETS: {
|
|
40
39
|
readonly owner_ru_clone: {
|
|
41
40
|
readonly language: "ru";
|
|
@@ -68,17 +67,15 @@ export declare const VOICE_PRESETS: {
|
|
|
68
67
|
readonly model: "eleven_v3";
|
|
69
68
|
};
|
|
70
69
|
};
|
|
71
|
-
/**
|
|
72
|
-
|
|
73
|
-
* список имён и метадата не разъехались (правило проекта №2 — не дублировать).
|
|
74
|
-
*/
|
|
70
|
+
/** Preset name tuple for `z.enum`, derived from `VOICE_PRESETS` keys so names and */
|
|
71
|
+
/** metadata cannot drift. */
|
|
75
72
|
export declare const VOICE_PRESET_NAMES: [keyof typeof VOICE_PRESETS, ...(keyof typeof VOICE_PRESETS)[]];
|
|
76
73
|
export type VoicePresetName = keyof typeof VOICE_PRESETS;
|
|
77
|
-
/**
|
|
74
|
+
/** The shape of a voice name, not a list: the catalog grows on the server without client releases. */
|
|
78
75
|
export declare const VOICE_NAME_PATTERN: RegExp;
|
|
79
76
|
export declare function isVoicePresetName(name: string): name is VoicePresetName;
|
|
80
|
-
/**
|
|
81
|
-
/**
|
|
77
|
+
/** The `/v1/voices` response as a schema, not a guess on both sides: the server sends a */
|
|
78
|
+
/** body parsed by it and the SDK parses with it. `Array.isArray` let any array through. */
|
|
82
79
|
export declare const voiceCatalogEntry: z.ZodObject<{
|
|
83
80
|
name: z.ZodString;
|
|
84
81
|
kind: z.ZodOptional<z.ZodString>;
|
|
@@ -135,28 +132,18 @@ export declare const voicesResponse: z.ZodObject<{
|
|
|
135
132
|
}, z.core.$strip>>>;
|
|
136
133
|
}, z.core.$strip>;
|
|
137
134
|
export type VoicesResponse = z.infer<typeof voicesResponse>;
|
|
138
|
-
/**
|
|
139
|
-
/** История выбора дефолта — docs/16. */
|
|
135
|
+
/** The voice when the actor's gender is unknown; the resolver fills it in, not the schema. */
|
|
140
136
|
export declare const DEFAULT_VOICE_PRESET: "george";
|
|
141
|
-
/**
|
|
137
|
+
/** The default voice follows the actor's gender. */
|
|
142
138
|
export declare const DEFAULT_VOICE_BY_GENDER: {
|
|
143
139
|
readonly female: "sarah";
|
|
144
140
|
readonly male: "george";
|
|
145
141
|
};
|
|
146
|
-
/**
|
|
147
|
-
|
|
148
|
-
* ElevenLabs не документирует формат id, наблюдаемые id — 20 символов, но
|
|
149
|
-
* строгая длина 20 превратила бы будущий валидный id иной длины в ложный
|
|
150
|
-
* отказ. Это лишь грубый фильтр мусора на входе; реальную проверку
|
|
151
|
-
* существования id делает бесплатный префлайт в воркере (US-515).
|
|
152
|
-
*/
|
|
142
|
+
/** Shape of a raw vendor `voice_id`, deliberately loose (16–32 alphanumerics): the format is */
|
|
143
|
+
/** undocumented. A coarse input filter only; the worker's free preflight checks existence. */
|
|
153
144
|
export declare const VOICE_ID_PATTERN: RegExp;
|
|
154
|
-
/**
|
|
155
|
-
|
|
156
|
-
* несёт НАШЕ имя (адаптер превратит его в вендорский id через `PRESET_TO_VOICE_ID`),
|
|
157
|
-
* `raw` — уже сырой вендорский id (escape hatch). Core НЕ знает вендорских id, и
|
|
158
|
-
* это объединение — граница: дальше маппинг делает адаптер (принцип 5).
|
|
159
|
-
*/
|
|
145
|
+
/** Resolved voice selection. `preset` carries OUR name, mapped to a vendor id by the adapter; */
|
|
146
|
+
/** `raw` is already a vendor id. Core never knows vendor ids. */
|
|
160
147
|
export type VoiceSelection = {
|
|
161
148
|
kind: "preset";
|
|
162
149
|
preset: VoicePresetName;
|
|
@@ -167,68 +154,39 @@ export type VoiceSelection = {
|
|
|
167
154
|
kind: "raw";
|
|
168
155
|
voiceId: string;
|
|
169
156
|
};
|
|
170
|
-
/**
|
|
171
|
-
/**
|
|
157
|
+
/** Priority: raw `voice_id` > `voice` > voice by actor gender > `DEFAULT_VOICE_PRESET`. */
|
|
158
|
+
/** Gender comes from `voiceGenderFor` (actors.ts); `makeUgcInput` enforces exclusivity. */
|
|
172
159
|
export declare function resolveVoiceSelection(input: {
|
|
173
160
|
voice?: string | undefined;
|
|
174
161
|
voice_id?: string | undefined;
|
|
175
162
|
}, gender?: Gender | undefined): VoiceSelection;
|
|
176
|
-
/**
|
|
177
|
-
/**
|
|
163
|
+
/** Model: explicit `tts_model` → the preset's model → default (a raw `voice_id` has no preset). */
|
|
164
|
+
/** A catalog voice speaks the default model, like the presets. */
|
|
178
165
|
export declare function resolveTtsModel(input: {
|
|
179
166
|
voice?: string | undefined;
|
|
180
167
|
voice_id?: string | undefined;
|
|
181
168
|
tts_model?: TtsModelId | undefined;
|
|
182
169
|
}, gender?: Gender | undefined): TtsModelId;
|
|
183
|
-
/**
|
|
184
|
-
|
|
185
|
-
* (US-517). Считает ТОЛЬКО буквенные символы: кириллица (U+0400–U+04FF) против
|
|
186
|
-
* латиницы (A–Z/a–z). Знаки препинания, цифры, пробелы, эмодзи игнорируются —
|
|
187
|
-
* они не различают язык.
|
|
188
|
-
*
|
|
189
|
-
* НЕЙТРАЛЬНАЯ ПОЛОСА (детерминированно, тестируемо):
|
|
190
|
-
* - < 2 букв всего (пусто, одни цифры, единичная буква-случайность) → "neutral":
|
|
191
|
-
* данных для суждения нет (это «мало букв» из плана §Р3, условие ii);
|
|
192
|
-
* - ни одна сторона не набрала порога доминирования 60% (напр. смесь 50/50) →
|
|
193
|
-
* "neutral": язык скрипта неоднозначен, warning выдавать не на чем.
|
|
194
|
-
* Только уверенное доминирование (≥60%) даёт "cyrillic"/"latin".
|
|
195
|
-
*/
|
|
170
|
+
/** Script family for the language warning: counts only Cyrillic vs Latin letters. Fewer than */
|
|
171
|
+
/** 2 letters or no side reaching this 60% dominance gives "neutral". */
|
|
196
172
|
export declare const SCRIPT_DOMINANCE_THRESHOLD = 0.6;
|
|
197
|
-
/**
|
|
198
|
-
/**
|
|
173
|
+
/** Letters of each script separately. `detectScriptFamily` sums them; apart they tell */
|
|
174
|
+
/** "few" from "none at all". */
|
|
199
175
|
export declare function scriptLetterCounts(script: string): {
|
|
200
176
|
cyrillic: number;
|
|
201
177
|
latin: number;
|
|
202
178
|
};
|
|
203
179
|
export declare function detectScriptFamily(script: string): "cyrillic" | "latin" | "neutral";
|
|
204
|
-
/**
|
|
180
|
+
/** QUOTE path only: a raw voice id is checked at run start, not at quote time. */
|
|
205
181
|
export declare const RAW_VOICE_ID_VALIDATION_WARNING = "raw voice_id is verified against your account at run start, not at quote time";
|
|
206
|
-
/**
|
|
207
|
-
|
|
208
|
-
* если ОДНОВРЕМЕННО: (i) причина — то, что клиент запросил ЯВНО (не дефолт, не наш
|
|
209
|
-
* выбор); (ii) утверждение опирается на данные, которые ЕСТЬ (лейбл присутствует),
|
|
210
|
-
* а не на их отсутствие; (iii) на дефолтном happy-path не срабатывает НИКОГДА.
|
|
211
|
-
*
|
|
212
|
-
* ПРАВИЛО СТАРШИНСТВА ПОДМЕНЫ: подмена (клиент попросил X, отдали Y) предупреждает
|
|
213
|
-
* ВСЕГДА, независимо от (iii) — (iii) глушит уведомления о ПРАВИЛЬНОМ поведении, не
|
|
214
|
-
* о деградированном. Канон такой подмены — 1080p→720p (render-backend.ts:144). В
|
|
215
|
-
* ДАННОМ дизайне подмены голоса НЕТ: путь падает closed (сырой id не найден → бросок
|
|
216
|
-
* до платного POST, US-515; неизвестное имя → 400 на границе enum), голос молча не
|
|
217
|
-
* заменяется. Поэтому правило старшинства здесь не применяется — но зафиксировано,
|
|
218
|
-
* чтобы будущая подмена не была ошибочно заглушена условием (iii).
|
|
219
|
-
*
|
|
220
|
-
* ПОЧЕМУ тест не подавляет обязательный warning про подмену №3 (принцип DR-2):
|
|
221
|
-
* обязанность №3 крепится к НЕИСПОЛНЕННОМУ явному запросу (клиент попросил 1080p,
|
|
222
|
-
* дали 720p) — это подмена, у неё старшинство над (iii). Здесь же голос либо
|
|
223
|
-
* исполняется как запрошен, либо ран падает — деградированной выдачи под видом
|
|
224
|
-
* успешной не бывает, глушить нечего.
|
|
225
|
-
*/
|
|
182
|
+
/** Voice warnings fire only when (i) the client chose explicitly, (ii) the claim rests on data */
|
|
183
|
+
/** present, (iii) never on the default happy path. A voice is never substituted, only failed. */
|
|
226
184
|
export declare function buildVoiceWarnings(input: {
|
|
227
185
|
voice?: string | undefined;
|
|
228
186
|
voice_id?: string | undefined;
|
|
229
187
|
script: string;
|
|
230
188
|
}): string[];
|
|
231
|
-
/**
|
|
189
|
+
/** A model that ignores stress marks on text where they matter. Silent on the v3 default. */
|
|
232
190
|
export declare function buildSpeechModelWarnings(input: {
|
|
233
191
|
voice?: string | undefined;
|
|
234
192
|
voice_id?: string | undefined;
|
package/dist/voices.js
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
|
-
/**
|
|
3
|
-
/**
|
|
2
|
+
/** ElevenLabs speech models the contract lets callers name. `multilingual_v2` was dropped: */
|
|
3
|
+
/** it honored none of the stress-marking methods we measured. */
|
|
4
4
|
export const TTS_MODELS = ["eleven_v3", "eleven_flash_v2_5", "eleven_turbo_v2_5"];
|
|
5
5
|
export const DEFAULT_TTS_MODEL = "eleven_v3";
|
|
6
|
-
/**
|
|
6
|
+
/** The v3 cap is the vendor's. For 2.5 the vendor allows 40,000; 10,000 is our run-length cap. */
|
|
7
7
|
export const TTS_MODEL_CHAR_CAP = {
|
|
8
8
|
eleven_v3: 5_000,
|
|
9
9
|
eleven_flash_v2_5: 10_000,
|
|
10
10
|
eleven_turbo_v2_5: 10_000,
|
|
11
11
|
};
|
|
12
|
-
/**
|
|
12
|
+
/** Only v3 reads the U+0301 stress mark; flash misreads the same mark three times as often. */
|
|
13
13
|
export const STRESS_MARK = String.fromCharCode(0x0301);
|
|
14
14
|
export const STRESS_MARK_MODELS = new Set(["eleven_v3"]);
|
|
15
15
|
const CYRILLIC_WORD = new RegExp(`[Ѐ-ӿ${STRESS_MARK}]+`, "g");
|
|
@@ -17,8 +17,8 @@ const CYRILLIC_STRESS_MARK = new RegExp(`[Ѐ-ӿ]${STRESS_MARK}`);
|
|
|
17
17
|
const CYRILLIC_LOWERCASE = /[а-џ]/;
|
|
18
18
|
const CYRILLIC_UPPERCASE = /[Ѐ-Я]/;
|
|
19
19
|
const CYRILLIC_UPPER_VOWELS = "АЕЁИОУЫЭЮЯ";
|
|
20
|
-
/**
|
|
21
|
-
/**
|
|
20
|
+
/** Index of the stressed capital: exactly one capital after the first letter, a vowel. */
|
|
21
|
+
/** A second capital or a consonant capital is an abbreviation, not stress. */
|
|
22
22
|
function stressCapitalIndex(word) {
|
|
23
23
|
if (!CYRILLIC_LOWERCASE.test(word))
|
|
24
24
|
return -1;
|
|
@@ -36,7 +36,8 @@ function stressCapitalIndex(word) {
|
|
|
36
36
|
}
|
|
37
37
|
return found;
|
|
38
38
|
}
|
|
39
|
-
/**
|
|
39
|
+
/** Turns an inner capital vowel into a stress mark. A word-initial capital stays a capital; */
|
|
40
|
+
/** an existing mark is not doubled. */
|
|
40
41
|
export function stressMarksFromCapitals(text) {
|
|
41
42
|
return text.replace(CYRILLIC_WORD, (word) => {
|
|
42
43
|
const i = stressCapitalIndex(word);
|
|
@@ -47,7 +48,7 @@ export function stressMarksFromCapitals(text) {
|
|
|
47
48
|
return word.slice(0, i) + vowel.toLowerCase() + mark + word.slice(i + 1);
|
|
48
49
|
});
|
|
49
50
|
}
|
|
50
|
-
/**
|
|
51
|
+
/** The text sent to the vendor: stress marking only for a model that reads it. */
|
|
51
52
|
export function speechTextFor(text, modelId) {
|
|
52
53
|
return STRESS_MARK_MODELS.has(modelId) ? stressMarksFromCapitals(text) : text;
|
|
53
54
|
}
|
|
@@ -62,7 +63,7 @@ export const STRESS_MARKING_DESCRIPTION = "Russian stress: write the stressed vo
|
|
|
62
63
|
"read as stress, so write \"Яндекс Еда\", not \"ЯндексЕда\". " +
|
|
63
64
|
"Tell users writing in Russian that they can mark stress this way. eleven_flash_v2_5 and " +
|
|
64
65
|
"eleven_turbo_v2_5 cost less but misread stress marks: capitals reach them unchanged.";
|
|
65
|
-
/**
|
|
66
|
+
/** Length is measured on the text sent to the vendor, stress marks included. */
|
|
66
67
|
export function ttsScriptLimitError(text, modelId) {
|
|
67
68
|
const model = TTS_MODELS.find((model) => model === modelId);
|
|
68
69
|
if (!model)
|
|
@@ -78,19 +79,17 @@ export function ttsScriptLimitError(text, modelId) {
|
|
|
78
79
|
}
|
|
79
80
|
return undefined;
|
|
80
81
|
}
|
|
81
|
-
/**
|
|
82
|
-
|
|
83
|
-
* презентации; дефолт задаётся отдельно через `DEFAULT_VOICE_PRESET`.
|
|
84
|
-
*/
|
|
82
|
+
/** The starter set of presets. Order matters only for presentation; the default is set */
|
|
83
|
+
/** separately by `DEFAULT_VOICE_PRESET`. */
|
|
85
84
|
export const VOICE_PRESETS = {
|
|
86
85
|
owner_ru_clone: {
|
|
87
86
|
language: "ru",
|
|
88
|
-
//
|
|
87
|
+
// The gender is OUR fact about our own clone, not vendor metadata.
|
|
89
88
|
gender: "male",
|
|
90
89
|
description: "Клон голоса владельца (David-Dmitry-Ru); русский — на английском даёт акцент",
|
|
91
90
|
model: "eleven_v3",
|
|
92
91
|
},
|
|
93
|
-
//
|
|
92
|
+
// Every preset is v3: more expressive, and the only model that reads stress marks.
|
|
94
93
|
sarah: {
|
|
95
94
|
language: "en",
|
|
96
95
|
gender: "female",
|
|
@@ -116,20 +115,18 @@ export const VOICE_PRESETS = {
|
|
|
116
115
|
model: "eleven_v3",
|
|
117
116
|
},
|
|
118
117
|
};
|
|
119
|
-
/**
|
|
120
|
-
|
|
121
|
-
* список имён и метадата не разъехались (правило проекта №2 — не дублировать).
|
|
122
|
-
*/
|
|
118
|
+
/** Preset name tuple for `z.enum`, derived from `VOICE_PRESETS` keys so names and */
|
|
119
|
+
/** metadata cannot drift. */
|
|
123
120
|
export const VOICE_PRESET_NAMES = Object.keys(VOICE_PRESETS);
|
|
124
|
-
/**
|
|
121
|
+
/** The shape of a voice name, not a list: the catalog grows on the server without client releases. */
|
|
125
122
|
export const VOICE_NAME_PATTERN = /^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/;
|
|
126
123
|
export function isVoicePresetName(name) {
|
|
127
124
|
return Object.hasOwn(VOICE_PRESETS, name);
|
|
128
125
|
}
|
|
129
|
-
/**
|
|
130
|
-
/**
|
|
126
|
+
/** The `/v1/voices` response as a schema, not a guess on both sides: the server sends a */
|
|
127
|
+
/** body parsed by it and the SDK parses with it. `Array.isArray` let any array through. */
|
|
131
128
|
export const voiceCatalogEntry = z.object({
|
|
132
|
-
//
|
|
129
|
+
// Strings, not enums: an unfamiliar name or label does not break `listVoices`.
|
|
133
130
|
name: z.string().min(1),
|
|
134
131
|
kind: z.string().optional(),
|
|
135
132
|
language: z.string().min(1).optional(),
|
|
@@ -142,7 +139,7 @@ export const voiceCatalogEntry = z.object({
|
|
|
142
139
|
model: z.enum(TTS_MODELS),
|
|
143
140
|
supported_models: z.array(z.string()).optional(),
|
|
144
141
|
verified_models: z.array(z.string()).optional(),
|
|
145
|
-
//
|
|
142
|
+
// The signature is temporary; a missing pair means the voice has no sample.
|
|
146
143
|
preview_url: z.url().optional(),
|
|
147
144
|
preview_expires_at: z.iso.datetime().optional(),
|
|
148
145
|
});
|
|
@@ -150,18 +147,17 @@ export const voiceModelEntry = z.object({
|
|
|
150
147
|
id: z.string().min(1),
|
|
151
148
|
char_limit: z.number().int().positive(),
|
|
152
149
|
languages: z.array(z.string()),
|
|
153
|
-
/**
|
|
154
|
-
/**
|
|
150
|
+
/** Whether the vendor verifies any library voice for this model at all. An empty */
|
|
151
|
+
/** `verified_models` on a model where this is `false` says nothing about the voice. */
|
|
155
152
|
vendor_verifies_voices: z.boolean().optional(),
|
|
156
153
|
});
|
|
157
154
|
export const voicesResponse = z.object({
|
|
158
155
|
voices: z.array(voiceCatalogEntry),
|
|
159
156
|
models: z.array(voiceModelEntry).optional(),
|
|
160
157
|
});
|
|
161
|
-
/**
|
|
162
|
-
/** История выбора дефолта — docs/16. */
|
|
158
|
+
/** The voice when the actor's gender is unknown; the resolver fills it in, not the schema. */
|
|
163
159
|
export const DEFAULT_VOICE_PRESET = "george";
|
|
164
|
-
/**
|
|
160
|
+
/** The default voice follows the actor's gender. */
|
|
165
161
|
export const DEFAULT_VOICE_BY_GENDER = {
|
|
166
162
|
female: "sarah",
|
|
167
163
|
male: "george",
|
|
@@ -169,30 +165,25 @@ export const DEFAULT_VOICE_BY_GENDER = {
|
|
|
169
165
|
function defaultVoiceFor(gender) {
|
|
170
166
|
return gender === undefined ? DEFAULT_VOICE_PRESET : DEFAULT_VOICE_BY_GENDER[gender];
|
|
171
167
|
}
|
|
172
|
-
/**
|
|
173
|
-
|
|
174
|
-
* ElevenLabs не документирует формат id, наблюдаемые id — 20 символов, но
|
|
175
|
-
* строгая длина 20 превратила бы будущий валидный id иной длины в ложный
|
|
176
|
-
* отказ. Это лишь грубый фильтр мусора на входе; реальную проверку
|
|
177
|
-
* существования id делает бесплатный префлайт в воркере (US-515).
|
|
178
|
-
*/
|
|
168
|
+
/** Shape of a raw vendor `voice_id`, deliberately loose (16–32 alphanumerics): the format is */
|
|
169
|
+
/** undocumented. A coarse input filter only; the worker's free preflight checks existence. */
|
|
179
170
|
export const VOICE_ID_PATTERN = /^[A-Za-z0-9]{16,32}$/;
|
|
180
|
-
/**
|
|
181
|
-
/**
|
|
171
|
+
/** Priority: raw `voice_id` > `voice` > voice by actor gender > `DEFAULT_VOICE_PRESET`. */
|
|
172
|
+
/** Gender comes from `voiceGenderFor` (actors.ts); `makeUgcInput` enforces exclusivity. */
|
|
182
173
|
export function resolveVoiceSelection(input, gender) {
|
|
183
174
|
if (input.voice_id !== undefined) {
|
|
184
175
|
return { kind: "raw", voiceId: input.voice_id };
|
|
185
176
|
}
|
|
186
177
|
if (input.voice !== undefined) {
|
|
187
|
-
//
|
|
178
|
+
// Not a preset, so a catalog slug: only the worker sidecar knows its id; a miss there refuses.
|
|
188
179
|
if (!isVoicePresetName(input.voice))
|
|
189
180
|
return { kind: "catalog", slug: input.voice };
|
|
190
181
|
return { kind: "preset", preset: input.voice };
|
|
191
182
|
}
|
|
192
183
|
return { kind: "preset", preset: defaultVoiceFor(gender) };
|
|
193
184
|
}
|
|
194
|
-
/**
|
|
195
|
-
/**
|
|
185
|
+
/** Model: explicit `tts_model` → the preset's model → default (a raw `voice_id` has no preset). */
|
|
186
|
+
/** A catalog voice speaks the default model, like the presets. */
|
|
196
187
|
export function resolveTtsModel(input, gender) {
|
|
197
188
|
if (input.tts_model !== undefined)
|
|
198
189
|
return input.tts_model;
|
|
@@ -201,22 +192,11 @@ export function resolveTtsModel(input, gender) {
|
|
|
201
192
|
const voice = input.voice ?? defaultVoiceFor(gender);
|
|
202
193
|
return isVoicePresetName(voice) ? VOICE_PRESETS[voice].model : DEFAULT_TTS_MODEL;
|
|
203
194
|
}
|
|
204
|
-
/**
|
|
205
|
-
|
|
206
|
-
* (US-517). Считает ТОЛЬКО буквенные символы: кириллица (U+0400–U+04FF) против
|
|
207
|
-
* латиницы (A–Z/a–z). Знаки препинания, цифры, пробелы, эмодзи игнорируются —
|
|
208
|
-
* они не различают язык.
|
|
209
|
-
*
|
|
210
|
-
* НЕЙТРАЛЬНАЯ ПОЛОСА (детерминированно, тестируемо):
|
|
211
|
-
* - < 2 букв всего (пусто, одни цифры, единичная буква-случайность) → "neutral":
|
|
212
|
-
* данных для суждения нет (это «мало букв» из плана §Р3, условие ii);
|
|
213
|
-
* - ни одна сторона не набрала порога доминирования 60% (напр. смесь 50/50) →
|
|
214
|
-
* "neutral": язык скрипта неоднозначен, warning выдавать не на чем.
|
|
215
|
-
* Только уверенное доминирование (≥60%) даёт "cyrillic"/"latin".
|
|
216
|
-
*/
|
|
195
|
+
/** Script family for the language warning: counts only Cyrillic vs Latin letters. Fewer than */
|
|
196
|
+
/** 2 letters or no side reaching this 60% dominance gives "neutral". */
|
|
217
197
|
export const SCRIPT_DOMINANCE_THRESHOLD = 0.6;
|
|
218
|
-
/**
|
|
219
|
-
/**
|
|
198
|
+
/** Letters of each script separately. `detectScriptFamily` sums them; apart they tell */
|
|
199
|
+
/** "few" from "none at all". */
|
|
220
200
|
export function scriptLetterCounts(script) {
|
|
221
201
|
let cyrillic = 0;
|
|
222
202
|
let latin = 0;
|
|
@@ -233,7 +213,7 @@ export function scriptLetterCounts(script) {
|
|
|
233
213
|
export function detectScriptFamily(script) {
|
|
234
214
|
const { cyrillic, latin } = scriptLetterCounts(script);
|
|
235
215
|
const total = cyrillic + latin;
|
|
236
|
-
//
|
|
216
|
+
// Too few letters: nothing to judge the language by.
|
|
237
217
|
if (total < 2) {
|
|
238
218
|
return "neutral";
|
|
239
219
|
}
|
|
@@ -245,70 +225,47 @@ export function detectScriptFamily(script) {
|
|
|
245
225
|
}
|
|
246
226
|
return "neutral";
|
|
247
227
|
}
|
|
248
|
-
/**
|
|
249
|
-
|
|
250
|
-
* НАМЕРЕННО охватывает только языки, которые мы реально размечаем (ru/en): лейбл
|
|
251
|
-
* вне этого набора трактуется как «нет данных для сравнения» и глушит warning
|
|
252
|
-
* (условие ii — утверждение опирается на присутствующие данные, а не на догадку).
|
|
253
|
-
*/
|
|
228
|
+
/** Maps our short preset language label to a script family. Covers only ru/en on purpose: */
|
|
229
|
+
/** any other label means "no data to compare" and silences the warning. */
|
|
254
230
|
const LANGUAGE_TO_SCRIPT_FAMILY = {
|
|
255
231
|
ru: "cyrillic",
|
|
256
232
|
en: "latin",
|
|
257
233
|
};
|
|
258
|
-
/**
|
|
234
|
+
/** QUOTE path only: a raw voice id is checked at run start, not at quote time. */
|
|
259
235
|
export const RAW_VOICE_ID_VALIDATION_WARNING = "raw voice_id is verified against your account at run start, not at quote time";
|
|
260
|
-
/**
|
|
261
|
-
|
|
262
|
-
* если ОДНОВРЕМЕННО: (i) причина — то, что клиент запросил ЯВНО (не дефолт, не наш
|
|
263
|
-
* выбор); (ii) утверждение опирается на данные, которые ЕСТЬ (лейбл присутствует),
|
|
264
|
-
* а не на их отсутствие; (iii) на дефолтном happy-path не срабатывает НИКОГДА.
|
|
265
|
-
*
|
|
266
|
-
* ПРАВИЛО СТАРШИНСТВА ПОДМЕНЫ: подмена (клиент попросил X, отдали Y) предупреждает
|
|
267
|
-
* ВСЕГДА, независимо от (iii) — (iii) глушит уведомления о ПРАВИЛЬНОМ поведении, не
|
|
268
|
-
* о деградированном. Канон такой подмены — 1080p→720p (render-backend.ts:144). В
|
|
269
|
-
* ДАННОМ дизайне подмены голоса НЕТ: путь падает closed (сырой id не найден → бросок
|
|
270
|
-
* до платного POST, US-515; неизвестное имя → 400 на границе enum), голос молча не
|
|
271
|
-
* заменяется. Поэтому правило старшинства здесь не применяется — но зафиксировано,
|
|
272
|
-
* чтобы будущая подмена не была ошибочно заглушена условием (iii).
|
|
273
|
-
*
|
|
274
|
-
* ПОЧЕМУ тест не подавляет обязательный warning про подмену №3 (принцип DR-2):
|
|
275
|
-
* обязанность №3 крепится к НЕИСПОЛНЕННОМУ явному запросу (клиент попросил 1080p,
|
|
276
|
-
* дали 720p) — это подмена, у неё старшинство над (iii). Здесь же голос либо
|
|
277
|
-
* исполняется как запрошен, либо ран падает — деградированной выдачи под видом
|
|
278
|
-
* успешной не бывает, глушить нечего.
|
|
279
|
-
*/
|
|
236
|
+
/** Voice warnings fire only when (i) the client chose explicitly, (ii) the claim rests on data */
|
|
237
|
+
/** present, (iii) never on the default happy path. A voice is never substituted, only failed. */
|
|
280
238
|
export function buildVoiceWarnings(input) {
|
|
281
|
-
//
|
|
282
|
-
//
|
|
239
|
+
// A raw id is an explicit choice, but its existence is checked at run start,
|
|
240
|
+
// so the quote warns about deferred validation.
|
|
283
241
|
if (input.voice_id !== undefined) {
|
|
284
242
|
return [RAW_VOICE_ID_VALIDATION_WARNING];
|
|
285
243
|
}
|
|
286
|
-
//
|
|
287
|
-
//
|
|
244
|
+
// No explicit voice: no language warnings ever. The gender-based voice is
|
|
245
|
+
// covered by `buildActorGenderWarnings` (actors.ts).
|
|
288
246
|
if (input.voice === undefined) {
|
|
289
247
|
return [];
|
|
290
248
|
}
|
|
291
|
-
// Defensive:
|
|
292
|
-
//
|
|
249
|
+
// Defensive: zod already narrowed voice, but a pure function does not rely on it.
|
|
250
|
+
// An unknown name has no preset and so no data.
|
|
293
251
|
if (!isVoicePresetName(input.voice)) {
|
|
294
252
|
return [];
|
|
295
253
|
}
|
|
296
254
|
const preset = VOICE_PRESETS[input.voice];
|
|
297
|
-
//
|
|
298
|
-
//
|
|
255
|
+
// Compare only against a PRESENT usable label; a language outside ru/en means
|
|
256
|
+
// no data, so stay silent instead of guessing.
|
|
299
257
|
const voiceFamily = LANGUAGE_TO_SCRIPT_FAMILY[preset.language];
|
|
300
258
|
if (voiceFamily === undefined) {
|
|
301
259
|
return [];
|
|
302
260
|
}
|
|
303
|
-
//
|
|
304
|
-
//
|
|
305
|
-
// Нейтральный/неоднозначный скрипт не противоречит ничему → молчим (ii).
|
|
261
|
+
// An explicit preset against the detected script family. A neutral or mixed
|
|
262
|
+
// script contradicts nothing.
|
|
306
263
|
const scriptFamily = detectScriptFamily(input.script);
|
|
307
264
|
if (scriptFamily === "neutral" || scriptFamily === voiceFamily) {
|
|
308
265
|
return [];
|
|
309
266
|
}
|
|
310
|
-
//
|
|
311
|
-
//
|
|
267
|
+
// "No letters at all" and "wrong language" call for different fixes: the text or
|
|
268
|
+
// its delivery versus the voice choice.
|
|
312
269
|
const counts = scriptLetterCounts(input.script);
|
|
313
270
|
if (counts[voiceFamily] === 0) {
|
|
314
271
|
return [
|
|
@@ -321,12 +278,12 @@ export function buildVoiceWarnings(input) {
|
|
|
321
278
|
`voice "${input.voice}" is labeled ${preset.language}, but the script looks ${scriptFamily}; it will be voiced in a ${preset.language} voice`,
|
|
322
279
|
];
|
|
323
280
|
}
|
|
324
|
-
/**
|
|
281
|
+
/** A model that ignores stress marks on text where they matter. Silent on the v3 default. */
|
|
325
282
|
export function buildSpeechModelWarnings(input, gender) {
|
|
326
283
|
const model = resolveTtsModel(input, gender);
|
|
327
284
|
if (STRESS_MARK_MODELS.has(model))
|
|
328
285
|
return [];
|
|
329
|
-
//
|
|
286
|
+
// A mark after a Latin letter is a French or Spanish accent (NFD), not stress.
|
|
330
287
|
const marked = CYRILLIC_STRESS_MARK.test(input.script) || stressMarksFromCapitals(input.script) !== input.script;
|
|
331
288
|
if (marked) {
|
|
332
289
|
return [
|