@leiboss/dsh-tts 0.0.0-stage → 0.3.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 +111 -0
- package/LICENSE +21 -0
- package/README.md +374 -2
- package/cordis.patch.yml +4 -0
- package/dist/audio/mp3-duration.d.ts +23 -0
- package/dist/audio/mp3-duration.d.ts.map +1 -0
- package/dist/audio/mp3-duration.js +140 -0
- package/dist/audio/mp3-duration.js.map +1 -0
- package/dist/audio-route.d.ts +122 -0
- package/dist/audio-route.d.ts.map +1 -0
- package/dist/audio-route.js +408 -0
- package/dist/audio-route.js.map +1 -0
- package/dist/cache.d.ts +50 -0
- package/dist/cache.d.ts.map +1 -0
- package/dist/cache.js +105 -0
- package/dist/cache.js.map +1 -0
- package/dist/chunked-tts.d.ts +34 -0
- package/dist/chunked-tts.d.ts.map +1 -0
- package/dist/chunked-tts.js +121 -0
- package/dist/chunked-tts.js.map +1 -0
- package/dist/chunker.d.ts +19 -0
- package/dist/chunker.d.ts.map +1 -0
- package/dist/chunker.js +98 -0
- package/dist/chunker.js.map +1 -0
- package/dist/client.js +90 -0
- package/dist/concurrency.d.ts +33 -0
- package/dist/concurrency.d.ts.map +1 -0
- package/dist/concurrency.js +95 -0
- package/dist/concurrency.js.map +1 -0
- package/dist/config.d.ts +174 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +186 -0
- package/dist/config.js.map +1 -0
- package/dist/edge-tts.d.ts +74 -0
- package/dist/edge-tts.d.ts.map +1 -0
- package/dist/edge-tts.js +161 -0
- package/dist/edge-tts.js.map +1 -0
- package/dist/engine-health.d.ts +108 -0
- package/dist/engine-health.d.ts.map +1 -0
- package/dist/engine-health.js +170 -0
- package/dist/engine-health.js.map +1 -0
- package/dist/engines/edge-js.d.ts +51 -0
- package/dist/engines/edge-js.d.ts.map +1 -0
- package/dist/engines/edge-js.js +98 -0
- package/dist/engines/edge-js.js.map +1 -0
- package/dist/errors.d.ts +66 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +110 -0
- package/dist/errors.js.map +1 -0
- package/dist/fallback.d.ts +69 -0
- package/dist/fallback.d.ts.map +1 -0
- package/dist/fallback.js +89 -0
- package/dist/fallback.js.map +1 -0
- package/dist/index.d.ts +72 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +723 -0
- package/dist/index.js.map +1 -0
- package/dist/probe.d.ts +42 -0
- package/dist/probe.d.ts.map +1 -0
- package/dist/probe.js +63 -0
- package/dist/probe.js.map +1 -0
- package/dist/provider.d.ts +33 -0
- package/dist/provider.d.ts.map +1 -0
- package/dist/provider.js +7 -0
- package/dist/provider.js.map +1 -0
- package/dist/register-service.d.ts +25 -0
- package/dist/register-service.d.ts.map +1 -0
- package/dist/register-service.js +33 -0
- package/dist/register-service.js.map +1 -0
- package/dist/sapi-tts.d.ts +29 -0
- package/dist/sapi-tts.d.ts.map +1 -0
- package/dist/sapi-tts.js +131 -0
- package/dist/sapi-tts.js.map +1 -0
- package/dist/self-check.d.ts +32 -0
- package/dist/self-check.d.ts.map +1 -0
- package/dist/self-check.js +75 -0
- package/dist/self-check.js.map +1 -0
- package/dist/service.d.ts +119 -0
- package/dist/service.d.ts.map +1 -0
- package/dist/service.js +404 -0
- package/dist/service.js.map +1 -0
- package/dist/voices.d.ts +21 -0
- package/dist/voices.d.ts.map +1 -0
- package/dist/voices.js +74 -0
- package/dist/voices.js.map +1 -0
- package/icon.svg +6 -0
- package/locale/en.json +6 -0
- package/locale/zh.json +6 -0
- package/package.json +93 -3
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure-JS MP3 timing, so duration and subtitle alignment never depend on
|
|
3
|
+
* ffmpeg or an external probe. Edge-TTS writes `audio-24khz-*` MPEG-2 Layer III
|
|
4
|
+
* (576 samples per frame), but the parser covers MPEG-1/2/2.5 Layer III.
|
|
5
|
+
*
|
|
6
|
+
* Cache metadata and the manifest both promise a `timingQuality`, so a parse
|
|
7
|
+
* failure must be reported as "unknown", never guessed here.
|
|
8
|
+
*/
|
|
9
|
+
/** MPEG-1 Layer III bitrates in kbit/s, indexed by the 4-bit field. */
|
|
10
|
+
const BITRATE_V1_L3 = [0, 32, 40, 48, 56, 64, 80, 96, 112, 128, 160, 192, 224, 256, 320, 0];
|
|
11
|
+
/** MPEG-2 and MPEG-2.5 Layer III bitrates in kbit/s. */
|
|
12
|
+
const BITRATE_V2_L3 = [0, 8, 16, 24, 32, 40, 48, 56, 64, 80, 96, 112, 128, 144, 160, 0];
|
|
13
|
+
/** Sample rates by MPEG version bits: 3 = MPEG-1, 2 = MPEG-2, 0 = MPEG-2.5. */
|
|
14
|
+
const SAMPLE_RATES = {
|
|
15
|
+
3: [44100, 48000, 32000],
|
|
16
|
+
2: [22050, 24000, 16000],
|
|
17
|
+
0: [11025, 12000, 8000],
|
|
18
|
+
};
|
|
19
|
+
/** Samples per frame for Layer III, by MPEG version. */
|
|
20
|
+
const SAMPLES_PER_FRAME = { 3: 1152, 2: 576, 0: 576 };
|
|
21
|
+
/** Read a 28-bit ID3v2 syncsafe integer. */
|
|
22
|
+
function syncsafe(b, offset) {
|
|
23
|
+
return (((b[offset] ?? 0) & 0x7f) * 0x200000 +
|
|
24
|
+
((b[offset + 1] ?? 0) & 0x7f) * 0x4000 +
|
|
25
|
+
((b[offset + 2] ?? 0) & 0x7f) * 0x80 +
|
|
26
|
+
((b[offset + 3] ?? 0) & 0x7f));
|
|
27
|
+
}
|
|
28
|
+
/** Byte offset of the first audio frame, skipping ID3v2 tags and their footer. */
|
|
29
|
+
function firstFrameOffset(b) {
|
|
30
|
+
if (b.length < 10)
|
|
31
|
+
return 0;
|
|
32
|
+
if (b[0] !== 0x49 || b[1] !== 0x44 || b[2] !== 0x33)
|
|
33
|
+
return 0;
|
|
34
|
+
const flags = b[5] ?? 0;
|
|
35
|
+
const size = syncsafe(b, 6);
|
|
36
|
+
const footer = (flags & 0x10) !== 0 ? 10 : 0;
|
|
37
|
+
return Math.min(b.length, 10 + size + footer);
|
|
38
|
+
}
|
|
39
|
+
/** Decode a Layer III frame header, or return undefined when it is not one. */
|
|
40
|
+
function readHeader(b, offset) {
|
|
41
|
+
if (offset + 4 > b.length)
|
|
42
|
+
return undefined;
|
|
43
|
+
if (b[offset] !== 0xff)
|
|
44
|
+
return undefined;
|
|
45
|
+
const b1 = b[offset + 1] ?? 0;
|
|
46
|
+
if ((b1 & 0xe0) !== 0xe0)
|
|
47
|
+
return undefined;
|
|
48
|
+
const version = (b1 >> 3) & 0x03;
|
|
49
|
+
const layer = (b1 >> 1) & 0x03;
|
|
50
|
+
// Layer III only: layer bits 01.
|
|
51
|
+
if (layer !== 0x01)
|
|
52
|
+
return undefined;
|
|
53
|
+
if (version === 1)
|
|
54
|
+
return undefined;
|
|
55
|
+
const b2 = b[offset + 2] ?? 0;
|
|
56
|
+
const bitrateIndex = (b2 >> 4) & 0x0f;
|
|
57
|
+
const sampleRateIndex = (b2 >> 2) & 0x03;
|
|
58
|
+
const padding = (b2 >> 1) & 0x01;
|
|
59
|
+
const bitrateTable = version === 3 ? BITRATE_V1_L3 : BITRATE_V2_L3;
|
|
60
|
+
const bitrate = (bitrateTable[bitrateIndex] ?? 0) * 1000;
|
|
61
|
+
const rates = SAMPLE_RATES[version];
|
|
62
|
+
const sampleRate = rates?.[sampleRateIndex] ?? 0;
|
|
63
|
+
const samplesPerFrame = SAMPLES_PER_FRAME[version] ?? 0;
|
|
64
|
+
if (bitrate <= 0 || sampleRate <= 0 || samplesPerFrame <= 0)
|
|
65
|
+
return undefined;
|
|
66
|
+
// Layer III uses 144 (MPEG-1) or 72 (MPEG-2/2.5) as the frame-length factor.
|
|
67
|
+
const factor = version === 3 ? 144 : 72;
|
|
68
|
+
const frameLength = Math.floor((factor * bitrate) / sampleRate) + padding;
|
|
69
|
+
if (frameLength < 24)
|
|
70
|
+
return undefined;
|
|
71
|
+
return { frameLength, sampleRate, samplesPerFrame, bitrate, version };
|
|
72
|
+
}
|
|
73
|
+
/** Find `Xing`/`Info` in the first frame and read its declared frame count. */
|
|
74
|
+
function readXingFrames(b, start, header) {
|
|
75
|
+
const limit = Math.min(b.length - 12, start + header.frameLength);
|
|
76
|
+
for (let i = start + 4; i <= limit; i += 1) {
|
|
77
|
+
const isXing = b[i] === 0x58 && b[i + 1] === 0x69 && b[i + 2] === 0x6e && b[i + 3] === 0x67;
|
|
78
|
+
const isInfo = b[i] === 0x49 && b[i + 1] === 0x6e && b[i + 2] === 0x66 && b[i + 3] === 0x6f;
|
|
79
|
+
if (!isXing && !isInfo)
|
|
80
|
+
continue;
|
|
81
|
+
const flags = b.readUInt32BE(i + 4);
|
|
82
|
+
// Frame count is the first optional field, present when bit 0 is set.
|
|
83
|
+
if ((flags & 0x01) === 0)
|
|
84
|
+
return undefined;
|
|
85
|
+
const frames = b.readUInt32BE(i + 8);
|
|
86
|
+
return frames > 0 ? frames : undefined;
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Measure an MP3 buffer by walking its frames. Returns undefined for anything
|
|
92
|
+
* that is not a Layer III stream this parser understands, so callers can fall
|
|
93
|
+
* back to an explicit "estimated" timing quality instead of silently lying.
|
|
94
|
+
*/
|
|
95
|
+
export function measureMp3(buffer) {
|
|
96
|
+
if (buffer.length < 4)
|
|
97
|
+
return undefined;
|
|
98
|
+
const start = firstFrameOffset(buffer);
|
|
99
|
+
const header = readHeader(buffer, start);
|
|
100
|
+
if (header === undefined)
|
|
101
|
+
return undefined;
|
|
102
|
+
const declared = readXingFrames(buffer, start, header);
|
|
103
|
+
if (declared !== undefined) {
|
|
104
|
+
return {
|
|
105
|
+
frames: declared,
|
|
106
|
+
sampleRate: header.sampleRate,
|
|
107
|
+
durationMs: Math.round((declared * header.samplesPerFrame * 1000) / header.sampleRate),
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
let offset = start;
|
|
111
|
+
let frames = 0;
|
|
112
|
+
while (offset + 4 <= buffer.length) {
|
|
113
|
+
const current = readHeader(buffer, offset);
|
|
114
|
+
if (current === undefined) {
|
|
115
|
+
// Resync: ID3 padding or a damaged frame. Scan a bounded window so a
|
|
116
|
+
// corrupt tail cannot turn into an O(n^2) walk.
|
|
117
|
+
let resynced = false;
|
|
118
|
+
for (let scan = offset + 1; scan < Math.min(buffer.length - 4, offset + 4096); scan += 1) {
|
|
119
|
+
if (readHeader(buffer, scan) !== undefined) {
|
|
120
|
+
offset = scan;
|
|
121
|
+
resynced = true;
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (!resynced)
|
|
126
|
+
break;
|
|
127
|
+
continue;
|
|
128
|
+
}
|
|
129
|
+
frames += 1;
|
|
130
|
+
offset += current.frameLength;
|
|
131
|
+
}
|
|
132
|
+
if (frames === 0)
|
|
133
|
+
return undefined;
|
|
134
|
+
return {
|
|
135
|
+
frames,
|
|
136
|
+
sampleRate: header.sampleRate,
|
|
137
|
+
durationMs: Math.round((frames * header.samplesPerFrame * 1000) / header.sampleRate),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=mp3-duration.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"mp3-duration.js","sourceRoot":"","sources":["../../src/audio/mp3-duration.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import type { IncomingMessage, ServerResponse } from 'node:http';
|
|
2
|
+
/** Path prefix owned by this plugin. The signed token is the next segment. */
|
|
3
|
+
export declare const AUDIO_ROUTE_PREFIX = "/dsh-tts/audio";
|
|
4
|
+
/** Signed URLs stay valid for six hours by default. */
|
|
5
|
+
export declare const DEFAULT_URL_TTL_MS: number;
|
|
6
|
+
/** Structural view of a DSH web route registration. */
|
|
7
|
+
export interface RegisterableRoute {
|
|
8
|
+
kind: 'exact' | 'prefix';
|
|
9
|
+
path: string;
|
|
10
|
+
handler: (req: IncomingMessage, res: ServerResponse) => void | Promise<void>;
|
|
11
|
+
}
|
|
12
|
+
/** Structural view of the host `webServer` service; only `register` is used. */
|
|
13
|
+
export interface HttpRouteRegistrar {
|
|
14
|
+
register(route: RegisterableRoute): () => void;
|
|
15
|
+
/** Advertised bind host, when the host exposes it. */
|
|
16
|
+
host?: string;
|
|
17
|
+
/** Actual listening port, when the host exposes it. */
|
|
18
|
+
port?: number;
|
|
19
|
+
/** Scheme advertised by the host, when available. */
|
|
20
|
+
protocol?: string;
|
|
21
|
+
/** Origin advertised by the host, when available. */
|
|
22
|
+
getOrigin?(): string | undefined;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Host-provided trust fence. DSH 0.2.2 exposes this as `ctx.connection`;
|
|
26
|
+
* `admit` is intentionally not used so links remain shareable to a browser
|
|
27
|
+
* that has no DSH session cookie.
|
|
28
|
+
*/
|
|
29
|
+
export interface ConnectionFence {
|
|
30
|
+
requestRejection?(input: {
|
|
31
|
+
headers: Record<string, string | string[] | undefined>;
|
|
32
|
+
}): 401 | 403 | undefined;
|
|
33
|
+
allowsRemoteAuthorities?: boolean;
|
|
34
|
+
}
|
|
35
|
+
/** Name of the persisted HMAC secret inside the output directory. */
|
|
36
|
+
export declare const SECRET_FILE_NAME = ".audio-route-secret";
|
|
37
|
+
/** Resolves a local audio path to a browser-openable URL, when routable. */
|
|
38
|
+
export interface AudioUrlProvider {
|
|
39
|
+
urlFor(filePath: string): string | undefined;
|
|
40
|
+
/** Absolute URL for human-readable output; absent host origin yields `undefined`. */
|
|
41
|
+
absoluteUrlFor?(filePath: string): string | undefined;
|
|
42
|
+
}
|
|
43
|
+
export interface AudioRouteOptions {
|
|
44
|
+
/** Ledger root; tokens may only name files beneath it. */
|
|
45
|
+
outputDir: string;
|
|
46
|
+
/** Token lifetime in milliseconds. */
|
|
47
|
+
ttlMs?: number;
|
|
48
|
+
/** Override the per-instance HMAC secret (tests only). */
|
|
49
|
+
secret?: Buffer;
|
|
50
|
+
/** Path of the persisted secret; defaults to `<outputDir>/.audio-route-secret`. */
|
|
51
|
+
secretPath?: string;
|
|
52
|
+
/** Injectable clock in epoch milliseconds. */
|
|
53
|
+
now?: () => number;
|
|
54
|
+
/** Hostnames allowed in `Host`; unset means any hostname (port still checked). */
|
|
55
|
+
allowedHosts?: readonly string[];
|
|
56
|
+
/** Expected listen port; mismatched `Host` ports are rejected. */
|
|
57
|
+
expectedPort?: number;
|
|
58
|
+
/** Debug sink for origin rejections; receives no token or path. */
|
|
59
|
+
debug?: (message: string) => void;
|
|
60
|
+
/** Extra trusted Host names (reverse proxies); disables port pinning. */
|
|
61
|
+
trustedHosts?: readonly string[];
|
|
62
|
+
/** Host trust fence, when DSH exposes `ctx.connection`. */
|
|
63
|
+
connection?: ConnectionFence;
|
|
64
|
+
/**
|
|
65
|
+
* URL and route prefix. Defaults to the engine's own prefix; a consumer such as
|
|
66
|
+
* the voiceover plugin passes its own so two plugins never contend for one path.
|
|
67
|
+
*/
|
|
68
|
+
prefix?: string;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Owns the signed-token format and the HTTP handler. One instance per plugin
|
|
72
|
+
* load; `register` binds it to the host router and returns a disposer.
|
|
73
|
+
*/
|
|
74
|
+
export declare class AudioRoute implements AudioUrlProvider {
|
|
75
|
+
private readonly root;
|
|
76
|
+
private readonly prefix;
|
|
77
|
+
private readonly ttlMs;
|
|
78
|
+
private readonly secret;
|
|
79
|
+
private readonly now;
|
|
80
|
+
private readonly debug?;
|
|
81
|
+
private readonly trustedHosts;
|
|
82
|
+
private readonly connection?;
|
|
83
|
+
private allowedHosts?;
|
|
84
|
+
private expectedPort?;
|
|
85
|
+
private skipPortCheck;
|
|
86
|
+
private active;
|
|
87
|
+
private origin?;
|
|
88
|
+
constructor(options: AudioRouteOptions);
|
|
89
|
+
/** True once the host router accepted the route. */
|
|
90
|
+
get registered(): boolean;
|
|
91
|
+
/** Register on the DSH host router; the disposer unregisters and disables URLs. */
|
|
92
|
+
register(server: HttpRouteRegistrar): () => void;
|
|
93
|
+
/**
|
|
94
|
+
* Browser-openable same-origin URL, or `undefined` when the route is not
|
|
95
|
+
* registered or the file is outside the output directory / not an MP3.
|
|
96
|
+
*/
|
|
97
|
+
urlFor(filePath: string): string | undefined;
|
|
98
|
+
/** Remember the host-advertised origin, trimmed of any trailing slash. */
|
|
99
|
+
setOrigin(origin: string): void;
|
|
100
|
+
/**
|
|
101
|
+
* Absolute playback URL for humans, or `undefined` when the host did not
|
|
102
|
+
* advertise an origin. Never guesses a host: callers fall back to the
|
|
103
|
+
* relative {@link urlFor} result instead.
|
|
104
|
+
*/
|
|
105
|
+
absoluteUrlFor(filePath: string): string | undefined;
|
|
106
|
+
/** Serve one request. Exported behaviour is exercised directly by tests. */
|
|
107
|
+
handle(req: IncomingMessage, res: ServerResponse): Promise<void>;
|
|
108
|
+
/** HMAC-SHA-256 over the payload, base64url-encoded. */
|
|
109
|
+
private sign;
|
|
110
|
+
/**
|
|
111
|
+
* Same-origin fence in front of the token check. Returns a reason when the
|
|
112
|
+
* request must be rejected. Missing headers are tolerated only when neither
|
|
113
|
+
* a port nor an Origin pins the request to a browser origin, which keeps
|
|
114
|
+
* non-browser callers working while DSH provides no trust-fence API.
|
|
115
|
+
*/
|
|
116
|
+
private checkOrigin;
|
|
117
|
+
/** Path relative to the ledger root, using `/` on every platform. */
|
|
118
|
+
private relativePath;
|
|
119
|
+
/** Verify signature + expiry, then confine the decoded path to the root. */
|
|
120
|
+
private verify;
|
|
121
|
+
}
|
|
122
|
+
//# sourceMappingURL=audio-route.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audio-route.d.ts","sourceRoot":"","sources":["../src/audio-route.ts"],"names":[],"mappings":"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"}
|