@pipedog/core 0.1.0 → 0.2.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/dist/audio/resamplers/engine.d.ts +60 -0
- package/dist/audio/resamplers/engine.js +89 -0
- package/dist/audio/resamplers/sinc_engine.d.ts +10 -0
- package/dist/audio/resamplers/sinc_engine.js +235 -0
- package/dist/audio/resamplers/soxr_resampler.d.ts +10 -6
- package/dist/audio/resamplers/soxr_resampler.js +17 -7
- package/dist/audio/resamplers/soxr_stream_resampler.d.ts +9 -6
- package/dist/audio/resamplers/soxr_stream_resampler.js +24 -11
- package/dist/audio/turn/smart_turn/local_smart_turn_v3.js +16 -8
- package/dist/audio/vad/vad_analyzer.d.ts +11 -3
- package/dist/audio/vad/vad_analyzer.js +72 -34
- package/dist/pipeline/worker.d.ts +7 -0
- package/dist/pipeline/worker.js +40 -23
- package/dist/utils/file_storage.d.ts +37 -21
- package/dist/utils/file_storage.js +87 -9
- package/dist/utils/frame_queue.js +2 -2
- package/dist/utils/image.d.ts +0 -1
- package/dist/utils/image.js +37 -4
- package/dist/utils/jpeg_encoder.d.ts +14 -0
- package/dist/utils/jpeg_encoder.js +315 -0
- package/dist/utils/text/transforms/acronyms.d.ts +21 -0
- package/dist/utils/text/transforms/acronyms.js +31 -0
- package/dist/utils/text/transforms/currency.d.ts +16 -0
- package/dist/utils/text/transforms/currency.js +56 -0
- package/dist/utils/text/transforms/dates.d.ts +18 -0
- package/dist/utils/text/transforms/dates.js +102 -0
- package/dist/utils/text/transforms/email.d.ts +17 -0
- package/dist/utils/text/transforms/email.js +35 -0
- package/dist/utils/text/transforms/index.d.ts +37 -0
- package/dist/utils/text/transforms/index.js +42 -0
- package/dist/utils/text/transforms/numbers.d.ts +25 -0
- package/dist/utils/text/transforms/numbers.js +59 -0
- package/dist/utils/text/transforms/percentages.d.ts +16 -0
- package/dist/utils/text/transforms/percentages.js +32 -0
- package/dist/utils/text/transforms/phone.d.ts +21 -0
- package/dist/utils/text/transforms/phone.js +39 -0
- package/dist/utils/text/transforms/pronunciations.js +2 -1
- package/dist/utils/text/transforms/replacements.d.ts +39 -0
- package/dist/utils/text/transforms/replacements.js +46 -0
- package/dist/utils/text/transforms/strip_markdown.d.ts +25 -0
- package/dist/utils/text/transforms/strip_markdown.js +60 -0
- package/dist/utils/text/transforms/units.d.ts +22 -0
- package/dist/utils/text/transforms/units.js +81 -0
- package/dist/utils/text/transforms/voice_formatter.d.ts +86 -0
- package/dist/utils/text/transforms/voice_formatter.js +98 -0
- package/dist/utils/text/unicode_digits.d.ts +3 -0
- package/dist/utils/text/unicode_digits.js +34 -0
- package/dist/utils/tracing/service_decorators.js +2 -2
- package/dist/utils/tracing/setup.d.ts +18 -6
- package/dist/utils/tracing/setup.js +63 -13
- package/dist/utils/tracing/turn_trace_observer.d.ts +5 -0
- package/dist/utils/tracing/turn_trace_observer.js +18 -4
- package/dist/utils/yaml.d.ts +151 -36
- package/dist/utils/yaml.js +527 -12
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/workers/proxy/index.d.ts +3 -0
- package/dist/workers/proxy/index.js +9 -0
- package/dist/workers/proxy/websocket/client.d.ts +110 -0
- package/dist/workers/proxy/websocket/client.js +196 -0
- package/dist/workers/proxy/websocket/index.d.ts +3 -0
- package/dist/workers/proxy/websocket/index.js +10 -0
- package/package.json +18 -3
|
@@ -0,0 +1,315 @@
|
|
|
1
|
+
// Copyright (c) 2026, pipedog contributors
|
|
2
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
3
|
+
/**
|
|
4
|
+
* A small baseline JPEG encoder (JFIF, 8-bit, three components, no chroma
|
|
5
|
+
* subsampling) in plain TypeScript.
|
|
6
|
+
*
|
|
7
|
+
* It stands in for Pillow's `Image.save(format="JPEG")`, which upstream uses
|
|
8
|
+
* to encode raw video frames for a model to look at. Upstream needs no more
|
|
9
|
+
* than a valid JPEG of the right size, so this encoder keeps to the simplest
|
|
10
|
+
* profile every decoder supports: the standard quantization and Huffman
|
|
11
|
+
* tables of ITU-T T.81 Annex K, scaled by quality like libjpeg.
|
|
12
|
+
*
|
|
13
|
+
* Runs everywhere core does: no dependencies, no `node:*`, no WebAssembly.
|
|
14
|
+
*/
|
|
15
|
+
// Zigzag order: ZIGZAG[i] is the row-major index of the i-th coefficient.
|
|
16
|
+
const ZIGZAG = new Uint8Array([
|
|
17
|
+
0, 1, 8, 16, 9, 2, 3, 10, 17, 24, 32, 25, 18, 11, 4, 5, 12, 19, 26, 33, 40, 48, 41, 34, 27, 20,
|
|
18
|
+
13, 6, 7, 14, 21, 28, 35, 42, 49, 56, 57, 50, 43, 36, 29, 22, 15, 23, 30, 37, 44, 51, 58, 59, 52,
|
|
19
|
+
45, 38, 31, 39, 46, 53, 60, 61, 54, 47, 55, 62, 63,
|
|
20
|
+
]);
|
|
21
|
+
// Annex K.1 quantization tables, row-major.
|
|
22
|
+
const LUMINANCE_QUANT = [
|
|
23
|
+
16, 11, 10, 16, 24, 40, 51, 61, 12, 12, 14, 19, 26, 58, 60, 55, 14, 13, 16, 24, 40, 57, 69, 56,
|
|
24
|
+
14, 17, 22, 29, 51, 87, 80, 62, 18, 22, 37, 56, 68, 109, 103, 77, 24, 35, 55, 64, 81, 104, 113,
|
|
25
|
+
92, 49, 64, 78, 87, 103, 121, 120, 101, 72, 92, 95, 98, 112, 100, 103, 99,
|
|
26
|
+
];
|
|
27
|
+
const CHROMINANCE_QUANT = [
|
|
28
|
+
17, 18, 24, 47, 99, 99, 99, 99, 18, 21, 26, 66, 99, 99, 99, 99, 24, 26, 56, 99, 99, 99, 99, 99,
|
|
29
|
+
47, 66, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
|
|
30
|
+
99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99, 99,
|
|
31
|
+
];
|
|
32
|
+
const DC_LUMINANCE = {
|
|
33
|
+
counts: [0, 1, 5, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0],
|
|
34
|
+
symbols: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
|
35
|
+
};
|
|
36
|
+
const DC_CHROMINANCE = {
|
|
37
|
+
counts: [0, 3, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0],
|
|
38
|
+
symbols: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
|
|
39
|
+
};
|
|
40
|
+
const AC_LUMINANCE = {
|
|
41
|
+
counts: [0, 2, 1, 3, 3, 2, 4, 3, 5, 5, 4, 4, 0, 0, 1, 0x7d],
|
|
42
|
+
symbols: [
|
|
43
|
+
0x01, 0x02, 0x03, 0x00, 0x04, 0x11, 0x05, 0x12, 0x21, 0x31, 0x41, 0x06, 0x13, 0x51, 0x61, 0x07,
|
|
44
|
+
0x22, 0x71, 0x14, 0x32, 0x81, 0x91, 0xa1, 0x08, 0x23, 0x42, 0xb1, 0xc1, 0x15, 0x52, 0xd1, 0xf0,
|
|
45
|
+
0x24, 0x33, 0x62, 0x72, 0x82, 0x09, 0x0a, 0x16, 0x17, 0x18, 0x19, 0x1a, 0x25, 0x26, 0x27, 0x28,
|
|
46
|
+
0x29, 0x2a, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49,
|
|
47
|
+
0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69,
|
|
48
|
+
0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89,
|
|
49
|
+
0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7,
|
|
50
|
+
0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3, 0xc4, 0xc5,
|
|
51
|
+
0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xe1, 0xe2,
|
|
52
|
+
0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf1, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
|
53
|
+
0xf9, 0xfa,
|
|
54
|
+
],
|
|
55
|
+
};
|
|
56
|
+
const AC_CHROMINANCE = {
|
|
57
|
+
counts: [0, 2, 1, 2, 4, 4, 3, 4, 7, 5, 4, 4, 0, 1, 2, 0x77],
|
|
58
|
+
symbols: [
|
|
59
|
+
0x00, 0x01, 0x02, 0x03, 0x11, 0x04, 0x05, 0x21, 0x31, 0x06, 0x12, 0x41, 0x51, 0x07, 0x61, 0x71,
|
|
60
|
+
0x13, 0x22, 0x32, 0x81, 0x08, 0x14, 0x42, 0x91, 0xa1, 0xb1, 0xc1, 0x09, 0x23, 0x33, 0x52, 0xf0,
|
|
61
|
+
0x15, 0x62, 0x72, 0xd1, 0x0a, 0x16, 0x24, 0x34, 0xe1, 0x25, 0xf1, 0x17, 0x18, 0x19, 0x1a, 0x26,
|
|
62
|
+
0x27, 0x28, 0x29, 0x2a, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3a, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48,
|
|
63
|
+
0x49, 0x4a, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68,
|
|
64
|
+
0x69, 0x6a, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7a, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
|
|
65
|
+
0x88, 0x89, 0x8a, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x9a, 0xa2, 0xa3, 0xa4, 0xa5,
|
|
66
|
+
0xa6, 0xa7, 0xa8, 0xa9, 0xaa, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6, 0xb7, 0xb8, 0xb9, 0xba, 0xc2, 0xc3,
|
|
67
|
+
0xc4, 0xc5, 0xc6, 0xc7, 0xc8, 0xc9, 0xca, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda,
|
|
68
|
+
0xe2, 0xe3, 0xe4, 0xe5, 0xe6, 0xe7, 0xe8, 0xe9, 0xea, 0xf2, 0xf3, 0xf4, 0xf5, 0xf6, 0xf7, 0xf8,
|
|
69
|
+
0xf9, 0xfa,
|
|
70
|
+
],
|
|
71
|
+
};
|
|
72
|
+
/** Canonical Huffman codes (Annex C) for a table: symbol → [code, length]. */
|
|
73
|
+
function buildCodes(spec) {
|
|
74
|
+
const codes = new Uint16Array(256);
|
|
75
|
+
const lengths = new Uint8Array(256);
|
|
76
|
+
let code = 0;
|
|
77
|
+
let k = 0;
|
|
78
|
+
for (let length = 1; length <= 16; length++) {
|
|
79
|
+
for (let i = 0; i < spec.counts[length - 1]; i++) {
|
|
80
|
+
const symbol = spec.symbols[k++];
|
|
81
|
+
codes[symbol] = code++;
|
|
82
|
+
lengths[symbol] = length;
|
|
83
|
+
}
|
|
84
|
+
code <<= 1;
|
|
85
|
+
}
|
|
86
|
+
return { codes, lengths };
|
|
87
|
+
}
|
|
88
|
+
/** A quantization table scaled for `quality` (1-100) the way libjpeg does. */
|
|
89
|
+
function scaleQuant(base, quality) {
|
|
90
|
+
const q = Math.min(100, Math.max(1, Math.round(quality)));
|
|
91
|
+
const scale = q < 50 ? Math.floor(5000 / q) : 200 - q * 2;
|
|
92
|
+
const out = new Uint8Array(64);
|
|
93
|
+
for (let i = 0; i < 64; i++) {
|
|
94
|
+
out[i] = Math.min(255, Math.max(1, Math.floor((base[i] * scale + 50) / 100)));
|
|
95
|
+
}
|
|
96
|
+
return out;
|
|
97
|
+
}
|
|
98
|
+
// cos((2x + 1) u π / 16) for the forward DCT, indexed [u * 8 + x].
|
|
99
|
+
const COS = new Float64Array(64);
|
|
100
|
+
for (let u = 0; u < 8; u++) {
|
|
101
|
+
for (let x = 0; x < 8; x++)
|
|
102
|
+
COS[u * 8 + x] = Math.cos(((2 * x + 1) * u * Math.PI) / 16);
|
|
103
|
+
}
|
|
104
|
+
/** Forward 8x8 DCT-II (separable), in place on a row-major block of level-shifted samples. */
|
|
105
|
+
function fdct(block, tmp) {
|
|
106
|
+
for (let y = 0; y < 8; y++) {
|
|
107
|
+
for (let u = 0; u < 8; u++) {
|
|
108
|
+
let sum = 0;
|
|
109
|
+
for (let x = 0; x < 8; x++)
|
|
110
|
+
sum += block[y * 8 + x] * COS[u * 8 + x];
|
|
111
|
+
tmp[y * 8 + u] = sum * (u === 0 ? Math.SQRT1_2 : 1) * 0.5;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
for (let u = 0; u < 8; u++) {
|
|
115
|
+
for (let v = 0; v < 8; v++) {
|
|
116
|
+
let sum = 0;
|
|
117
|
+
for (let y = 0; y < 8; y++)
|
|
118
|
+
sum += tmp[y * 8 + u] * COS[v * 8 + y];
|
|
119
|
+
block[v * 8 + u] = sum * (v === 0 ? Math.SQRT1_2 : 1) * 0.5;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
/** Growable byte buffer with an entropy-coded bit writer (with 0xFF byte stuffing). */
|
|
124
|
+
class ByteWriter {
|
|
125
|
+
#buf = new Uint8Array(1024);
|
|
126
|
+
#len = 0;
|
|
127
|
+
#bitBuf = 0;
|
|
128
|
+
#bitCount = 0;
|
|
129
|
+
byte(b) {
|
|
130
|
+
if (this.#len === this.#buf.length) {
|
|
131
|
+
const next = new Uint8Array(this.#buf.length * 2);
|
|
132
|
+
next.set(this.#buf);
|
|
133
|
+
this.#buf = next;
|
|
134
|
+
}
|
|
135
|
+
this.#buf[this.#len++] = b & 0xff;
|
|
136
|
+
}
|
|
137
|
+
word(w) {
|
|
138
|
+
this.byte(w >> 8);
|
|
139
|
+
this.byte(w);
|
|
140
|
+
}
|
|
141
|
+
bytes(bs) {
|
|
142
|
+
for (let i = 0; i < bs.length; i++)
|
|
143
|
+
this.byte(bs[i]);
|
|
144
|
+
}
|
|
145
|
+
bits(value, count) {
|
|
146
|
+
for (let i = count - 1; i >= 0; i--) {
|
|
147
|
+
this.#bitBuf = (this.#bitBuf << 1) | ((value >> i) & 1);
|
|
148
|
+
this.#bitCount++;
|
|
149
|
+
if (this.#bitCount === 8) {
|
|
150
|
+
this.byte(this.#bitBuf);
|
|
151
|
+
if (this.#bitBuf === 0xff)
|
|
152
|
+
this.byte(0);
|
|
153
|
+
this.#bitBuf = 0;
|
|
154
|
+
this.#bitCount = 0;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
/** Pad the last entropy-coded byte with 1-bits (T.81 F.1.2.3). */
|
|
159
|
+
flushBits() {
|
|
160
|
+
if (this.#bitCount > 0)
|
|
161
|
+
this.bits((1 << (8 - this.#bitCount)) - 1, 8 - this.#bitCount);
|
|
162
|
+
}
|
|
163
|
+
result() {
|
|
164
|
+
return this.#buf.slice(0, this.#len);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** The magnitude category (bit length) of a coefficient, and its T.81 F.1.2.1 bits. */
|
|
168
|
+
function category(value) {
|
|
169
|
+
const abs = Math.abs(value);
|
|
170
|
+
let size = 0;
|
|
171
|
+
while (abs >> size)
|
|
172
|
+
size++;
|
|
173
|
+
const bits = value < 0 ? value + (1 << size) - 1 : value;
|
|
174
|
+
return [size, bits];
|
|
175
|
+
}
|
|
176
|
+
function writeHuffmanTable(w, tableClass, id, spec) {
|
|
177
|
+
w.byte((tableClass << 4) | id);
|
|
178
|
+
w.bytes(spec.counts);
|
|
179
|
+
w.bytes(spec.symbols);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Encode RGB pixels as a baseline JPEG.
|
|
183
|
+
*
|
|
184
|
+
* @param rgb Interleaved 8-bit RGB pixels, `width * height * 3` bytes, row-major.
|
|
185
|
+
* @param width Image width in pixels.
|
|
186
|
+
* @param height Image height in pixels.
|
|
187
|
+
* @returns The JPEG file bytes.
|
|
188
|
+
*/
|
|
189
|
+
export function encodeJpegRgb(rgb, width, height, options = {}) {
|
|
190
|
+
if (!Number.isInteger(width) || !Number.isInteger(height) || width < 1 || height < 1) {
|
|
191
|
+
throw new RangeError(`invalid image size ${width}x${height}`);
|
|
192
|
+
}
|
|
193
|
+
if (width > 0xffff || height > 0xffff) {
|
|
194
|
+
throw new RangeError(`image too large for JPEG: ${width}x${height}`);
|
|
195
|
+
}
|
|
196
|
+
if (rgb.length < width * height * 3) {
|
|
197
|
+
throw new RangeError("not enough image data");
|
|
198
|
+
}
|
|
199
|
+
const quality = options.quality ?? 75;
|
|
200
|
+
const quant = [scaleQuant(LUMINANCE_QUANT, quality), scaleQuant(CHROMINANCE_QUANT, quality)];
|
|
201
|
+
const dc = [buildCodes(DC_LUMINANCE), buildCodes(DC_CHROMINANCE)];
|
|
202
|
+
const ac = [buildCodes(AC_LUMINANCE), buildCodes(AC_CHROMINANCE)];
|
|
203
|
+
const w = new ByteWriter();
|
|
204
|
+
// SOI and a JFIF 1.01 APP0 segment (no thumbnail, 1:1 aspect).
|
|
205
|
+
w.word(0xffd8);
|
|
206
|
+
w.word(0xffe0);
|
|
207
|
+
w.word(16);
|
|
208
|
+
w.bytes([0x4a, 0x46, 0x49, 0x46, 0x00, 1, 1, 0]);
|
|
209
|
+
w.word(1);
|
|
210
|
+
w.word(1);
|
|
211
|
+
w.bytes([0, 0]);
|
|
212
|
+
// DQT: both tables, 8-bit precision, in zigzag order.
|
|
213
|
+
w.word(0xffdb);
|
|
214
|
+
w.word(2 + 2 * 65);
|
|
215
|
+
for (let t = 0; t < 2; t++) {
|
|
216
|
+
w.byte(t);
|
|
217
|
+
for (let i = 0; i < 64; i++)
|
|
218
|
+
w.byte(quant[t][ZIGZAG[i]]);
|
|
219
|
+
}
|
|
220
|
+
// SOF0: baseline, 8-bit, three components, no subsampling.
|
|
221
|
+
w.word(0xffc0);
|
|
222
|
+
w.word(8 + 3 * 3);
|
|
223
|
+
w.byte(8);
|
|
224
|
+
w.word(height);
|
|
225
|
+
w.word(width);
|
|
226
|
+
w.byte(3);
|
|
227
|
+
for (let c = 0; c < 3; c++) {
|
|
228
|
+
w.byte(c + 1);
|
|
229
|
+
w.byte(0x11);
|
|
230
|
+
w.byte(c === 0 ? 0 : 1);
|
|
231
|
+
}
|
|
232
|
+
// DHT: the four Annex K tables.
|
|
233
|
+
w.word(0xffc4);
|
|
234
|
+
const dhtLength = 2 +
|
|
235
|
+
[DC_LUMINANCE, AC_LUMINANCE, DC_CHROMINANCE, AC_CHROMINANCE].reduce((n, s) => n + 17 + s.symbols.length, 0);
|
|
236
|
+
w.word(dhtLength);
|
|
237
|
+
writeHuffmanTable(w, 0, 0, DC_LUMINANCE);
|
|
238
|
+
writeHuffmanTable(w, 1, 0, AC_LUMINANCE);
|
|
239
|
+
writeHuffmanTable(w, 0, 1, DC_CHROMINANCE);
|
|
240
|
+
writeHuffmanTable(w, 1, 1, AC_CHROMINANCE);
|
|
241
|
+
// SOS: all three components in one interleaved scan.
|
|
242
|
+
w.word(0xffda);
|
|
243
|
+
w.word(6 + 2 * 3);
|
|
244
|
+
w.byte(3);
|
|
245
|
+
for (let c = 0; c < 3; c++) {
|
|
246
|
+
w.byte(c + 1);
|
|
247
|
+
w.byte(c === 0 ? 0x00 : 0x11);
|
|
248
|
+
}
|
|
249
|
+
w.bytes([0, 63, 0]);
|
|
250
|
+
const block = new Float64Array(64);
|
|
251
|
+
const tmp = new Float64Array(64);
|
|
252
|
+
const previousDc = [0, 0, 0];
|
|
253
|
+
for (let by = 0; by < height; by += 8) {
|
|
254
|
+
for (let bx = 0; bx < width; bx += 8) {
|
|
255
|
+
for (let c = 0; c < 3; c++) {
|
|
256
|
+
// Level-shifted YCbCr samples (JFIF), edge pixels repeated past the border.
|
|
257
|
+
for (let y = 0; y < 8; y++) {
|
|
258
|
+
const py = Math.min(by + y, height - 1);
|
|
259
|
+
for (let x = 0; x < 8; x++) {
|
|
260
|
+
const px = Math.min(bx + x, width - 1);
|
|
261
|
+
const i = (py * width + px) * 3;
|
|
262
|
+
const r = rgb[i];
|
|
263
|
+
const g = rgb[i + 1];
|
|
264
|
+
const b = rgb[i + 2];
|
|
265
|
+
block[y * 8 + x] =
|
|
266
|
+
c === 0
|
|
267
|
+
? 0.299 * r + 0.587 * g + 0.114 * b - 128
|
|
268
|
+
: c === 1
|
|
269
|
+
? -0.168736 * r - 0.331264 * g + 0.5 * b
|
|
270
|
+
: 0.5 * r - 0.418688 * g - 0.081312 * b;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
fdct(block, tmp);
|
|
274
|
+
const t = c === 0 ? 0 : 1;
|
|
275
|
+
const q = quant[t];
|
|
276
|
+
const coefficients = new Int32Array(64);
|
|
277
|
+
for (let i = 0; i < 64; i++) {
|
|
278
|
+
const k = ZIGZAG[i];
|
|
279
|
+
coefficients[i] = Math.round(block[k] / q[k]);
|
|
280
|
+
}
|
|
281
|
+
// DC: the difference from the previous block of this component.
|
|
282
|
+
const diff = coefficients[0] - previousDc[c];
|
|
283
|
+
previousDc[c] = coefficients[0];
|
|
284
|
+
const [dcSize, dcBits] = category(diff);
|
|
285
|
+
w.bits(dc[t].codes[dcSize], dc[t].lengths[dcSize]);
|
|
286
|
+
if (dcSize > 0)
|
|
287
|
+
w.bits(dcBits, dcSize);
|
|
288
|
+
// AC: run-length of zeros and magnitude category, then the bits.
|
|
289
|
+
let run = 0;
|
|
290
|
+
for (let i = 1; i < 64; i++) {
|
|
291
|
+
const value = coefficients[i];
|
|
292
|
+
if (value === 0) {
|
|
293
|
+
run++;
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
while (run > 15) {
|
|
297
|
+
w.bits(ac[t].codes[0xf0], ac[t].lengths[0xf0]);
|
|
298
|
+
run -= 16;
|
|
299
|
+
}
|
|
300
|
+
const [size, bits] = category(value);
|
|
301
|
+
const symbol = (run << 4) | size;
|
|
302
|
+
w.bits(ac[t].codes[symbol], ac[t].lengths[symbol]);
|
|
303
|
+
w.bits(bits, size);
|
|
304
|
+
run = 0;
|
|
305
|
+
}
|
|
306
|
+
if (run > 0)
|
|
307
|
+
w.bits(ac[t].codes[0x00], ac[t].lengths[0x00]);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
w.flushBits();
|
|
312
|
+
w.word(0xffd9);
|
|
313
|
+
return w.result();
|
|
314
|
+
}
|
|
315
|
+
//# sourceMappingURL=jpeg_encoder.js.map
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/** Normalize acronyms so TTS pronounces each letter individually. */
|
|
2
|
+
import type { TextType } from "../../../frames/frames.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Insert spaces between letters of uppercase acronyms.
|
|
5
|
+
*
|
|
6
|
+
* This transformer is alphanumeric-preserving: the same letters are kept,
|
|
7
|
+
* only spaces are added between them, so the `WordCompletionTracker`
|
|
8
|
+
* requires no segment-map overhead.
|
|
9
|
+
*
|
|
10
|
+
* @param text Input text possibly containing acronyms like API or HTTP.
|
|
11
|
+
* @param _textType Text type of the text frame (unused).
|
|
12
|
+
* @returns Text with acronyms letter-spaced (e.g. `"API"` → `"A P I"`).
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const result = await normalizeAcronyms("Use the API or HTTP endpoint", "*");
|
|
17
|
+
* // "Use the A P I or H T T P endpoint"
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeAcronyms(text: string, _textType: string | TextType): Promise<string>;
|
|
21
|
+
//# sourceMappingURL=acronyms.d.ts.map
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026, Daily
|
|
2
|
+
// Copyright (c) 2026, pipedog contributors
|
|
3
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
//
|
|
5
|
+
// Ported from pipecat: src/pipecat/utils/text/transforms/acronyms.py
|
|
6
|
+
// Two or more consecutive uppercase letters not immediately followed by a lowercase
|
|
7
|
+
// letter (to avoid splitting CamelCase words like "iPhone" or "McDonalds").
|
|
8
|
+
// Python's `\b` is Unicode-aware; the lookarounds rebuild it with Unicode `\w`
|
|
9
|
+
// (letters, digits and underscore).
|
|
10
|
+
const ACRONYM_RE = /(?<![\p{L}\p{N}_])[A-Z]{2,}(?![a-z])(?![\p{L}\p{N}_])/gu;
|
|
11
|
+
/**
|
|
12
|
+
* Insert spaces between letters of uppercase acronyms.
|
|
13
|
+
*
|
|
14
|
+
* This transformer is alphanumeric-preserving: the same letters are kept,
|
|
15
|
+
* only spaces are added between them, so the `WordCompletionTracker`
|
|
16
|
+
* requires no segment-map overhead.
|
|
17
|
+
*
|
|
18
|
+
* @param text Input text possibly containing acronyms like API or HTTP.
|
|
19
|
+
* @param _textType Text type of the text frame (unused).
|
|
20
|
+
* @returns Text with acronyms letter-spaced (e.g. `"API"` → `"A P I"`).
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* ```ts
|
|
24
|
+
* const result = await normalizeAcronyms("Use the API or HTTP endpoint", "*");
|
|
25
|
+
* // "Use the A P I or H T T P endpoint"
|
|
26
|
+
* ```
|
|
27
|
+
*/
|
|
28
|
+
export async function normalizeAcronyms(text, _textType) {
|
|
29
|
+
return text.replace(ACRONYM_RE, (match) => [...match].join(" "));
|
|
30
|
+
}
|
|
31
|
+
//# sourceMappingURL=acronyms.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { TextType } from "../../../frames/frames.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Expand currency amounts to their spoken form.
|
|
4
|
+
*
|
|
5
|
+
* @param text Input text possibly containing currency expressions.
|
|
6
|
+
* @param _textType Text type of the text frame (unused).
|
|
7
|
+
* @returns Text with currency amounts replaced by spoken equivalents.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```ts
|
|
11
|
+
* const result = await expandCurrency("Your balance is $42.50", "*");
|
|
12
|
+
* // "Your balance is forty-two dollars and fifty cents"
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export declare function expandCurrency(text: string, _textType: string | TextType): Promise<string>;
|
|
16
|
+
//# sourceMappingURL=currency.d.ts.map
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026, Daily
|
|
2
|
+
// Copyright (c) 2026, pipedog contributors
|
|
3
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
//
|
|
5
|
+
// Ported from pipecat: src/pipecat/utils/text/transforms/currency.py
|
|
6
|
+
/** Expand currency amounts into spoken form for TTS. */
|
|
7
|
+
import { num2words } from "num2words-ts";
|
|
8
|
+
import { toAsciiDigits } from "../unicode_digits.js";
|
|
9
|
+
/** Maps currency symbol to (singular, plural, cents_singular, cents_plural). */
|
|
10
|
+
const CURRENCY_MAP = {
|
|
11
|
+
$: ["dollar", "dollars", "cent", "cents"],
|
|
12
|
+
"€": ["euro", "euros", "cent", "cents"],
|
|
13
|
+
"£": ["pound", "pounds", "penny", "pence"],
|
|
14
|
+
"¥": ["yen", "yen", null, null],
|
|
15
|
+
"₹": ["rupee", "rupees", "paisa", "paise"],
|
|
16
|
+
};
|
|
17
|
+
// Python's `\b` (after a digit here) is Unicode-aware; the lookahead rebuilds
|
|
18
|
+
// it with Unicode `\w` (letters, digits and underscore).
|
|
19
|
+
// `\p{Nd}` is Python's Unicode `\d`; toAsciiDigits() lets BigInt/Number parse what int()/float() accept.
|
|
20
|
+
const CURRENCY_RE = /([€£¥₹$])\s*(\p{Nd}{1,3}(?:,\p{Nd}{3})*|\p{Nd}+)(?![\p{L}\p{N}_])(?:\.(\p{Nd}+))?/gu;
|
|
21
|
+
function amountToWords(n, singular, plural) {
|
|
22
|
+
const words = num2words(n, { lang: "en" });
|
|
23
|
+
const unit = n === 1n ? singular : plural;
|
|
24
|
+
return `${words} ${unit}`;
|
|
25
|
+
}
|
|
26
|
+
function currencyMatch(symbol, wholeMatch, fracStr) {
|
|
27
|
+
const wholeStr = wholeMatch.replaceAll(",", "");
|
|
28
|
+
const currency = CURRENCY_MAP[symbol] ?? ["unit", "units", "cent", "cents"];
|
|
29
|
+
const [singular, plural, cSingular, cPlural] = currency;
|
|
30
|
+
const whole = BigInt(toAsciiDigits(wholeStr));
|
|
31
|
+
let result = amountToWords(whole, singular, plural);
|
|
32
|
+
if (fracStr && cSingular && cPlural) {
|
|
33
|
+
const frac = BigInt(toAsciiDigits(fracStr.slice(0, 2).padEnd(2, "0")));
|
|
34
|
+
if (frac > 0n) {
|
|
35
|
+
result += ` and ${amountToWords(frac, cSingular, cPlural)}`;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return result;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Expand currency amounts to their spoken form.
|
|
42
|
+
*
|
|
43
|
+
* @param text Input text possibly containing currency expressions.
|
|
44
|
+
* @param _textType Text type of the text frame (unused).
|
|
45
|
+
* @returns Text with currency amounts replaced by spoken equivalents.
|
|
46
|
+
*
|
|
47
|
+
* @example
|
|
48
|
+
* ```ts
|
|
49
|
+
* const result = await expandCurrency("Your balance is $42.50", "*");
|
|
50
|
+
* // "Your balance is forty-two dollars and fifty cents"
|
|
51
|
+
* ```
|
|
52
|
+
*/
|
|
53
|
+
export async function expandCurrency(text, _textType) {
|
|
54
|
+
return text.replace(CURRENCY_RE, (_match, symbol, whole, frac) => currencyMatch(symbol, whole, frac));
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=currency.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { TextType } from "../../../frames/frames.ts";
|
|
2
|
+
/**
|
|
3
|
+
* Expand date expressions to their spoken form.
|
|
4
|
+
*
|
|
5
|
+
* Handles ISO format (`YYYY-MM-DD`) and US format (`MM/DD/YYYY` or `MM-DD-YYYY`).
|
|
6
|
+
*
|
|
7
|
+
* @param text Input text possibly containing date expressions.
|
|
8
|
+
* @param _textType Text type of the text frame (unused).
|
|
9
|
+
* @returns Text with date expressions replaced by spoken equivalents.
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```ts
|
|
13
|
+
* const result = await normalizeDates("Meeting on 2023-05-10", "*");
|
|
14
|
+
* // "Meeting on May 10th, two thousand and twenty three"
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeDates(text: string, _textType: string | TextType): Promise<string>;
|
|
18
|
+
//# sourceMappingURL=dates.d.ts.map
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026, Daily
|
|
2
|
+
// Copyright (c) 2026, pipedog contributors
|
|
3
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
//
|
|
5
|
+
// Ported from pipecat: src/pipecat/utils/text/transforms/dates.py
|
|
6
|
+
/** Expand date expressions into spoken form for TTS. */
|
|
7
|
+
import { num2words } from "num2words-ts";
|
|
8
|
+
import { toAsciiDigits } from "../unicode_digits.js";
|
|
9
|
+
const pyInt = (digits) => Number(toAsciiDigits(digits));
|
|
10
|
+
// Python's `\b` (next to a digit here) is Unicode-aware; the lookarounds
|
|
11
|
+
// rebuild it with Unicode `\w` (letters, digits and underscore).
|
|
12
|
+
// ISO dates: 2023-05-10
|
|
13
|
+
// `\p{Nd}` is Python's Unicode `\d`; toAsciiDigits() lets BigInt/Number parse what int()/float() accept.
|
|
14
|
+
const ISO_DATE_RE = /(?<![\p{L}\p{N}_])(\p{Nd}{4})-(\p{Nd}{2})-(\p{Nd}{2})(?![\p{L}\p{N}_])/gu;
|
|
15
|
+
// US dates: 05/10/2023 or 05-10-2023 (not matched by ISO because ISO has 4-digit year first)
|
|
16
|
+
const US_DATE_RE = /(?<![\p{L}\p{N}_])(\p{Nd}{1,2})[/-](\p{Nd}{1,2})[/-](\p{Nd}{4})(?![\p{L}\p{N}_])/gu;
|
|
17
|
+
const ORDINAL_SUFFIXES = { 1: "st", 2: "nd", 3: "rd" };
|
|
18
|
+
// Indexed by month - 1. Hardcoded rather than derived from strftime("%B"), which
|
|
19
|
+
// renders the month in the process's LC_TIME locale and would produce a
|
|
20
|
+
// mixed-language result alongside the English year and ordinal suffix.
|
|
21
|
+
const MONTH_NAMES = [
|
|
22
|
+
"January",
|
|
23
|
+
"February",
|
|
24
|
+
"March",
|
|
25
|
+
"April",
|
|
26
|
+
"May",
|
|
27
|
+
"June",
|
|
28
|
+
"July",
|
|
29
|
+
"August",
|
|
30
|
+
"September",
|
|
31
|
+
"October",
|
|
32
|
+
"November",
|
|
33
|
+
"December",
|
|
34
|
+
];
|
|
35
|
+
function ordinal(n) {
|
|
36
|
+
const key = [11, 12, 13].includes(n % 100) ? 0 : n % 10;
|
|
37
|
+
const suffix = ORDINAL_SUFFIXES[key] ?? "th";
|
|
38
|
+
return `${n}${suffix}`;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Build a date like Python's `datetime(year, month, day)`.
|
|
42
|
+
*
|
|
43
|
+
* @throws RangeError for an out-of-range year, month or day (Python's `ValueError`).
|
|
44
|
+
*/
|
|
45
|
+
function makeDate(year, month, day) {
|
|
46
|
+
if (year < 1 || year > 9999)
|
|
47
|
+
throw new RangeError(`year ${year} is out of range`);
|
|
48
|
+
if (month < 1 || month > 12)
|
|
49
|
+
throw new RangeError("month must be in 1..12");
|
|
50
|
+
const leap = (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0;
|
|
51
|
+
const daysInMonth = [31, leap ? 29 : 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31][month - 1];
|
|
52
|
+
if (day < 1 || day > daysInMonth)
|
|
53
|
+
throw new RangeError("day is out of range for month");
|
|
54
|
+
return { year, month, day };
|
|
55
|
+
}
|
|
56
|
+
function dateToSpoken(dt) {
|
|
57
|
+
const yearWords = num2words(dt.year, { lang: "en" });
|
|
58
|
+
const month = MONTH_NAMES[dt.month - 1];
|
|
59
|
+
const day = ordinal(dt.day);
|
|
60
|
+
return `${month} ${day}, ${yearWords}`;
|
|
61
|
+
}
|
|
62
|
+
function isoReplace(match, year, month, day) {
|
|
63
|
+
try {
|
|
64
|
+
return dateToSpoken(makeDate(pyInt(year), pyInt(month), pyInt(day)));
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
if (e instanceof RangeError)
|
|
68
|
+
return match;
|
|
69
|
+
throw e;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function usReplace(match, month, day, year) {
|
|
73
|
+
try {
|
|
74
|
+
return dateToSpoken(makeDate(pyInt(year), pyInt(month), pyInt(day)));
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
if (e instanceof RangeError)
|
|
78
|
+
return match;
|
|
79
|
+
throw e;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* Expand date expressions to their spoken form.
|
|
84
|
+
*
|
|
85
|
+
* Handles ISO format (`YYYY-MM-DD`) and US format (`MM/DD/YYYY` or `MM-DD-YYYY`).
|
|
86
|
+
*
|
|
87
|
+
* @param text Input text possibly containing date expressions.
|
|
88
|
+
* @param _textType Text type of the text frame (unused).
|
|
89
|
+
* @returns Text with date expressions replaced by spoken equivalents.
|
|
90
|
+
*
|
|
91
|
+
* @example
|
|
92
|
+
* ```ts
|
|
93
|
+
* const result = await normalizeDates("Meeting on 2023-05-10", "*");
|
|
94
|
+
* // "Meeting on May 10th, two thousand and twenty three"
|
|
95
|
+
* ```
|
|
96
|
+
*/
|
|
97
|
+
export async function normalizeDates(text, _textType) {
|
|
98
|
+
text = text.replace(ISO_DATE_RE, isoReplace);
|
|
99
|
+
text = text.replace(US_DATE_RE, usReplace);
|
|
100
|
+
return text;
|
|
101
|
+
}
|
|
102
|
+
//# sourceMappingURL=dates.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/** Transform email addresses into spoken form for TTS. */
|
|
2
|
+
import type { TextType } from "../../../frames/frames.ts";
|
|
3
|
+
/**
|
|
4
|
+
* Transform email addresses into their spoken form.
|
|
5
|
+
*
|
|
6
|
+
* @param text Input text possibly containing email addresses.
|
|
7
|
+
* @param _textType Text type of the text frame (unused).
|
|
8
|
+
* @returns Text with email addresses replaced by spoken equivalents.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const result = await emailToSpeech("Contact user@example.com today", "*");
|
|
13
|
+
* // "Contact user at example dot com today"
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export declare function emailToSpeech(text: string, _textType: string | TextType): Promise<string>;
|
|
17
|
+
//# sourceMappingURL=email.d.ts.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Copyright (c) 2024-2026, Daily
|
|
2
|
+
// Copyright (c) 2026, pipedog contributors
|
|
3
|
+
// SPDX-License-Identifier: BSD-2-Clause
|
|
4
|
+
//
|
|
5
|
+
// Ported from pipecat: src/pipecat/utils/text/transforms/email.py
|
|
6
|
+
const EMAIL_RE = /[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}/g;
|
|
7
|
+
function emailToSpoken(email) {
|
|
8
|
+
const at = email.indexOf("@");
|
|
9
|
+
const local = email.slice(0, at);
|
|
10
|
+
const domain = email.slice(at + 1);
|
|
11
|
+
const localSpoken = local
|
|
12
|
+
.replaceAll(".", " dot ")
|
|
13
|
+
.replaceAll("_", " underscore ")
|
|
14
|
+
.replaceAll("-", " dash ")
|
|
15
|
+
.replaceAll("+", " plus ");
|
|
16
|
+
const domainSpoken = domain.replaceAll(".", " dot ").replaceAll("-", " dash ");
|
|
17
|
+
return `${localSpoken} at ${domainSpoken}`;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Transform email addresses into their spoken form.
|
|
21
|
+
*
|
|
22
|
+
* @param text Input text possibly containing email addresses.
|
|
23
|
+
* @param _textType Text type of the text frame (unused).
|
|
24
|
+
* @returns Text with email addresses replaced by spoken equivalents.
|
|
25
|
+
*
|
|
26
|
+
* @example
|
|
27
|
+
* ```ts
|
|
28
|
+
* const result = await emailToSpeech("Contact user@example.com today", "*");
|
|
29
|
+
* // "Contact user at example dot com today"
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export async function emailToSpeech(text, _textType) {
|
|
33
|
+
return text.replace(EMAIL_RE, (match) => emailToSpoken(match));
|
|
34
|
+
}
|
|
35
|
+
//# sourceMappingURL=email.js.map
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Built-in text transformers for TTS voice formatting.
|
|
3
|
+
*
|
|
4
|
+
* Transformers are async callables with signature:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* async function transform(text: string, textType: string): Promise<string>
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* They are registered with a TTS service via `textTransforms`:
|
|
11
|
+
*
|
|
12
|
+
* ```ts
|
|
13
|
+
* const tts = new CartesiaTTSService({
|
|
14
|
+
* textTransforms: [["*", stripMarkdown], ["*", expandCurrency]],
|
|
15
|
+
* });
|
|
16
|
+
* ```
|
|
17
|
+
*
|
|
18
|
+
* Or use `VoiceFormatter` for a single configurable bundle:
|
|
19
|
+
*
|
|
20
|
+
* ```ts
|
|
21
|
+
* const tts = new CartesiaTTSService({
|
|
22
|
+
* textTransforms: [["*", new VoiceFormatter({ expandCurrency: true })]],
|
|
23
|
+
* });
|
|
24
|
+
* ```
|
|
25
|
+
*/
|
|
26
|
+
export { normalizeAcronyms } from "./acronyms.ts";
|
|
27
|
+
export { expandCurrency } from "./currency.ts";
|
|
28
|
+
export { normalizeDates } from "./dates.ts";
|
|
29
|
+
export { emailToSpeech } from "./email.ts";
|
|
30
|
+
export { expandNumbers } from "./numbers.ts";
|
|
31
|
+
export { expandPercentages } from "./percentages.ts";
|
|
32
|
+
export { expandPhoneNumbers } from "./phone.ts";
|
|
33
|
+
export { replaceText } from "./replacements.ts";
|
|
34
|
+
export { stripMarkdown } from "./strip_markdown.ts";
|
|
35
|
+
export { expandUnits } from "./units.ts";
|
|
36
|
+
export { VoiceFormatter } from "./voice_formatter.ts";
|
|
37
|
+
//# sourceMappingURL=index.d.ts.map
|