@vdegenne/gemini-tts-proxy 0.0.0-stage → 0.1.1

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/lib/ffmpeg.js ADDED
@@ -0,0 +1,113 @@
1
+ import { spawn } from 'node:child_process';
2
+ import { createWriteStream } from 'node:fs';
3
+ import { pipeline } from 'node:stream/promises';
4
+ const MIN_VERSION = [4, 4, 0];
5
+ export async function checkFFmpeg() {
6
+ const version = await new Promise(function (resolve, reject) {
7
+ const child = spawn('ffmpeg', ['-version'], {
8
+ stdio: ['ignore', 'pipe', 'pipe'],
9
+ });
10
+ let output = '';
11
+ child.stdout.setEncoding('utf8');
12
+ child.stdout.on('data', function (chunk) {
13
+ output += chunk;
14
+ });
15
+ child.on('error', reject);
16
+ child.on('close', function (code) {
17
+ if (code !== 0) {
18
+ reject(new Error('FFmpeg failed to execute successfully'));
19
+ return;
20
+ }
21
+ const match = output.match(/ffmpeg version n?(\d+)\.(\d+)(?:\.(\d+))?/);
22
+ if (!match) {
23
+ reject(new Error('Could not determine FFmpeg version'));
24
+ return;
25
+ }
26
+ const detected = [
27
+ Number(match[1]),
28
+ Number(match[2]),
29
+ Number(match[3] ?? 0),
30
+ ];
31
+ const isSupported = detected[0] > MIN_VERSION[0] ||
32
+ (detected[0] === MIN_VERSION[0] &&
33
+ (detected[1] > MIN_VERSION[1] ||
34
+ (detected[1] === MIN_VERSION[1] &&
35
+ detected[2] >= MIN_VERSION[2])));
36
+ if (!isSupported) {
37
+ reject(new Error(`FFmpeg 4.4.0 or newer is required (found ${match[0]})`));
38
+ return;
39
+ }
40
+ resolve(match[0]);
41
+ });
42
+ });
43
+ const encoders = await new Promise(function (resolve, reject) {
44
+ const child = spawn('ffmpeg', ['-hide_banner', '-encoders'], {
45
+ stdio: ['ignore', 'pipe', 'pipe'],
46
+ });
47
+ let output = '';
48
+ child.stdout.setEncoding('utf8');
49
+ child.stdout.on('data', function (chunk) {
50
+ output += chunk;
51
+ });
52
+ child.on('error', reject);
53
+ child.on('close', function (code) {
54
+ if (code !== 0) {
55
+ reject(new Error('Could not inspect FFmpeg encoders'));
56
+ return;
57
+ }
58
+ resolve(output);
59
+ });
60
+ });
61
+ if (!/^\s*A\S*\s+libmp3lame\s/m.test(encoders)) {
62
+ throw new Error('FFmpeg was found, but the libmp3lame encoder is missing');
63
+ }
64
+ return { version };
65
+ }
66
+ export async function encodePcmToMp3(pcm, outputPath, format) {
67
+ const child = spawn('ffmpeg', [
68
+ '-hide_banner',
69
+ '-loglevel',
70
+ 'error',
71
+ '-f',
72
+ format.bitsPerSample === 16 ? 's16le' : 's32le',
73
+ '-ar',
74
+ String(format.sampleRate),
75
+ '-ac',
76
+ String(format.channels),
77
+ '-i',
78
+ 'pipe:0',
79
+ '-c:a',
80
+ 'libmp3lame',
81
+ '-b:a',
82
+ '128k',
83
+ '-f',
84
+ 'mp3',
85
+ 'pipe:1',
86
+ ], {
87
+ stdio: ['pipe', 'pipe', 'pipe'],
88
+ });
89
+ let stderr = '';
90
+ child.stderr.setEncoding('utf8');
91
+ child.stderr.on('data', function (chunk) {
92
+ stderr += chunk;
93
+ });
94
+ const exitPromise = new Promise(function (resolve, reject) {
95
+ child.on('error', reject);
96
+ child.on('close', resolve);
97
+ });
98
+ try {
99
+ child.stdin.end(pcm);
100
+ const [, exitCode] = await Promise.all([
101
+ pipeline(child.stdout, createWriteStream(outputPath, { flags: 'wx' })),
102
+ exitPromise,
103
+ ]);
104
+ if (exitCode !== 0) {
105
+ throw new Error(`FFmpeg encoding failed (exit code ${exitCode}): ${stderr.trim()}`);
106
+ }
107
+ }
108
+ catch (error) {
109
+ child.kill();
110
+ throw error;
111
+ }
112
+ }
113
+ //# sourceMappingURL=ffmpeg.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ffmpeg.js","sourceRoot":"","sources":["../src/ffmpeg.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,KAAK,EAAC,MAAM,oBAAoB,CAAA;AACxC,OAAO,EAAC,iBAAiB,EAAC,MAAM,SAAS,CAAA;AACzC,OAAO,EAAC,QAAQ,EAAC,MAAM,sBAAsB,CAAA;AAE7C,MAAM,WAAW,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAA;AAW7B,MAAM,CAAC,KAAK,UAAU,WAAW;IAChC,MAAM,OAAO,GAAG,MAAM,IAAI,OAAO,CAAS,UAAU,OAAO,EAAE,MAAM;QAClE,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,UAAU,CAAC,EAAE;YAC3C,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SACjC,CAAC,CAAA;QAEF,IAAI,MAAM,GAAG,EAAE,CAAA;QAEf,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAChC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAa;YAC9C,MAAM,IAAI,KAAK,CAAA;QAChB,CAAC,CAAC,CAAA;QAEF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAEzB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,IAAI;YAC/B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBAChB,MAAM,CAAC,IAAI,KAAK,CAAC,uCAAuC,CAAC,CAAC,CAAA;gBAC1D,OAAM;YACP,CAAC;YAED,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,2CAA2C,CAAC,CAAA;YAEvE,IAAI,CAAC,KAAK,EAAE,CAAC;gBACZ,MAAM,CAAC,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC,CAAA;gBACvD,OAAM;YACP,CAAC;YAED,MAAM,QAAQ,GAAG;gBAChB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAChB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;gBAChB,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;aACrB,CAAA;YAED,MAAM,WAAW,GAChB,QAAQ,CAAC,CAAC,CAAE,GAAG,WAAW,CAAC,CAAC,CAAE;gBAC9B,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC;oBAC9B,CAAC,QAAQ,CAAC,CAAC,CAAE,GAAG,WAAW,CAAC,CAAC,CAAE;wBAC9B,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,WAAW,CAAC,CAAC,CAAC;4BAC9B,QAAQ,CAAC,CAAC,CAAE,IAAI,WAAW,CAAC,CAAC,CAAE,CAAC,CAAC,CAAC,CAAA;YAEtC,IAAI,CAAC,WAAW,EAAE,CAAC;gBAClB,MAAM,CACL,IAAI,KAAK,CAAC,4CAA4C,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAClE,CAAA;gBACD,OAAM;YACP,CAAC;YAED,OAAO,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAClB,CAAC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,MAAM,QAAQ,GAAG,MAAM,IAAI,OAAO,CAAS,UAAU,OAAO,EAAE,MAAM;QACnE,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,EAAE,CAAC,cAAc,EAAE,WAAW,CAAC,EAAE;YAC5D,KAAK,EAAE,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SACjC,CAAC,CAAA;QAEF,IAAI,MAAM,GAAG,EAAE,CAAA;QAEf,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;QAChC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAa;YAC9C,MAAM,IAAI,KAAK,CAAA;QAChB,CAAC,CAAC,CAAA;QAEF,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QAEzB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,UAAU,IAAI;YAC/B,IAAI,IAAI,KAAK,CAAC,EAAE,CAAC;gBAChB,MAAM,CAAC,IAAI,KAAK,CAAC,mCAAmC,CAAC,CAAC,CAAA;gBACtD,OAAM;YACP,CAAC;YAED,OAAO,CAAC,MAAM,CAAC,CAAA;QAChB,CAAC,CAAC,CAAA;IACH,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC,0BAA0B,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChD,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAA;IAC3E,CAAC;IAED,OAAO,EAAC,OAAO,EAAC,CAAA;AACjB,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,cAAc,CACnC,GAAW,EACX,UAAkB,EAClB,MAAiB;IAEjB,MAAM,KAAK,GAAG,KAAK,CAClB,QAAQ,EACR;QACC,cAAc;QACd,WAAW;QACX,OAAO;QACP,IAAI;QACJ,MAAM,CAAC,aAAa,KAAK,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,OAAO;QAC/C,KAAK;QACL,MAAM,CAAC,MAAM,CAAC,UAAU,CAAC;QACzB,KAAK;QACL,MAAM,CAAC,MAAM,CAAC,QAAQ,CAAC;QACvB,IAAI;QACJ,QAAQ;QACR,MAAM;QACN,YAAY;QACZ,MAAM;QACN,MAAM;QACN,IAAI;QACJ,KAAK;QACL,QAAQ;KACR,EACD;QACC,KAAK,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC;KAC/B,CACD,CAAA;IAED,IAAI,MAAM,GAAG,EAAE,CAAA;IAEf,KAAK,CAAC,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,CAAA;IAChC,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,UAAU,KAAa;QAC9C,MAAM,IAAI,KAAK,CAAA;IAChB,CAAC,CAAC,CAAA;IAEF,MAAM,WAAW,GAAG,IAAI,OAAO,CAAgB,UAAU,OAAO,EAAE,MAAM;QACvE,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;QACzB,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,OAAO,CAAC,CAAA;IAC3B,CAAC,CAAC,CAAA;IAEF,IAAI,CAAC;QACJ,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAEpB,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;YACtC,QAAQ,CAAC,KAAK,CAAC,MAAM,EAAE,iBAAiB,CAAC,UAAU,EAAE,EAAC,KAAK,EAAE,IAAI,EAAC,CAAC,CAAC;YACpE,WAAW;SACX,CAAC,CAAA;QAEF,IAAI,QAAQ,KAAK,CAAC,EAAE,CAAC;YACpB,MAAM,IAAI,KAAK,CACd,qCAAqC,QAAQ,MAAM,MAAM,CAAC,IAAI,EAAE,EAAE,CAClE,CAAA;QACF,CAAC;IACF,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,EAAE,CAAA;QACZ,MAAM,KAAK,CAAA;IACZ,CAAC;AACF,CAAC"}
package/lib/index.d.ts ADDED
@@ -0,0 +1,5 @@
1
+ #!/usr/bin/env node
2
+ import { Hono } from 'hono';
3
+ declare const app: Hono<import("hono/types").BlankEnv, import("hono/types").BlankSchema, "/">;
4
+ export { app };
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAA;AA4B3B,QAAA,MAAM,GAAG,4EAAa,CAAA;AAqOtB,OAAO,EAAE,GAAG,EAAE,CAAA"}
package/lib/index.js ADDED
@@ -0,0 +1,218 @@
1
+ #!/usr/bin/env node
2
+ import { serve } from '@hono/node-server';
3
+ import { Hono } from 'hono';
4
+ import { cors } from 'hono/cors';
5
+ import { Readable } from 'node:stream';
6
+ import { AccessError, resolveApiKey } from './access.js';
7
+ import { getSpeech } from './cache.js';
8
+ import { config } from './config.js';
9
+ import { GEMINI_TTS_MODELS, GEMINI_TTS_VOICES, } from './core.js';
10
+ import { checkFFmpeg } from './ffmpeg.js';
11
+ import { closeStats } from './stats.js';
12
+ let ffmpegInfo;
13
+ try {
14
+ ffmpegInfo = await checkFFmpeg();
15
+ }
16
+ catch (error) {
17
+ console.error('\nFFmpeg validation failed.');
18
+ console.error(error instanceof Error ? error.message : error);
19
+ console.error('Please install FFmpeg 4.4 or newer with libmp3lame support and make sure it is available in PATH.');
20
+ process.exit(1);
21
+ }
22
+ const app = new Hono();
23
+ app.use('*', cors({
24
+ origin: [
25
+ 'http://localhost:35107',
26
+ 'https://glaizme.web.app',
27
+ 'https://gl-ai-z.win',
28
+ ],
29
+ allowMethods: ['GET', 'POST', 'OPTIONS'],
30
+ allowHeaders: ['Content-Type', 'Authorization', 'x-goog-api-key'],
31
+ }));
32
+ app.get('/health', (c) => {
33
+ return c.json({ status: 'ok' });
34
+ });
35
+ app.get('/v1/voices', (c) => {
36
+ return c.json({
37
+ voices: GEMINI_TTS_VOICES.map(function (voice) {
38
+ return {
39
+ id: voice,
40
+ name: voice,
41
+ };
42
+ }),
43
+ });
44
+ });
45
+ app.get('/v1/audio/speech', async (c) => {
46
+ const input = c.req.query('input');
47
+ const model = c.req.query('model');
48
+ const voice = c.req.query('voice');
49
+ const languageCode = c.req.query('languageCode');
50
+ const systemInstruction = c.req.query('systemInstruction');
51
+ if (!input?.trim() ||
52
+ !model?.trim() ||
53
+ !voice?.trim() ||
54
+ !languageCode?.trim()) {
55
+ return c.json({
56
+ error: 'input, model, voice and languageCode must be non-empty strings',
57
+ }, 400);
58
+ }
59
+ if (!GEMINI_TTS_MODELS.includes(model)) {
60
+ return c.json({
61
+ error: `Unsupported model. Supported models: ${GEMINI_TTS_MODELS.join(', ')}`,
62
+ }, 400);
63
+ }
64
+ let apiKey;
65
+ try {
66
+ apiKey = await resolveApiKey(c.req.header('x-goog-api-key'), c.req.header('Authorization'));
67
+ }
68
+ catch (error) {
69
+ if (error instanceof AccessError) {
70
+ return c.json({ error: error.message }, error.status);
71
+ }
72
+ throw error;
73
+ }
74
+ try {
75
+ const audio = await getSpeech({
76
+ input,
77
+ model: model,
78
+ voice,
79
+ languageCode,
80
+ systemInstruction,
81
+ apiKey,
82
+ });
83
+ const stream = audio.body instanceof Readable
84
+ ? Readable.toWeb(audio.body)
85
+ : Readable.toWeb(Readable.from([audio.body]));
86
+ return new Response(stream, {
87
+ headers: {
88
+ 'content-type': 'audio/mpeg',
89
+ 'content-length': String(audio.size),
90
+ 'cache-control': 'private, max-age=31536000, immutable',
91
+ },
92
+ });
93
+ }
94
+ catch (error) {
95
+ console.error('Speech synthesis failed:', error);
96
+ return c.json({ error: 'Speech synthesis failed' }, 502);
97
+ }
98
+ });
99
+ app.post('/v1/audio/speech', async (c) => {
100
+ return c.json({ error: 'POST endpoint temporarily disabled' }, 410);
101
+ // let body: unknown
102
+ //
103
+ // try {
104
+ // body = await c.req.json()
105
+ // } catch {
106
+ // return c.json({ error: 'Invalid JSON body' }, 400)
107
+ // }
108
+ //
109
+ // if (body === null || typeof body !== 'object' || Array.isArray(body)) {
110
+ // return c.json({ error: 'Expected a JSON object' }, 400)
111
+ // }
112
+ //
113
+ // const input = body as Record<string, unknown>
114
+ //
115
+ // if (
116
+ // typeof input.input !== 'string' ||
117
+ // input.input.trim().length === 0 ||
118
+ // typeof input.voice !== 'string' ||
119
+ // input.voice.trim().length === 0
120
+ // ) {
121
+ // return c.json({ error: 'input and voice must be non-empty strings' }, 400)
122
+ // }
123
+ //
124
+ // const speed = input.speed ?? 1
125
+ // const volumeMultiplier = input.volume_multiplier ?? 1
126
+ //
127
+ // if (
128
+ // typeof speed !== 'number' ||
129
+ // !Number.isFinite(speed) ||
130
+ // speed <= 0 ||
131
+ // typeof volumeMultiplier !== 'number' ||
132
+ // !Number.isFinite(volumeMultiplier) ||
133
+ // volumeMultiplier <= 0
134
+ // ) {
135
+ // return c.json(
136
+ // { error: 'speed and volume_multiplier must be positive numbers' },
137
+ // 400,
138
+ // )
139
+ // }
140
+ //
141
+ // if (requiresPremium(input.input, input.voice)) {
142
+ // const authorization = c.req.header('Authorization')
143
+ //
144
+ // if (!authorization?.startsWith('Bearer ')) {
145
+ // return c.json({ error: 'Authentication required' }, 401)
146
+ // }
147
+ //
148
+ // try {
149
+ // const isPremium = await verifyPremiumUser(authorization.slice(7))
150
+ //
151
+ // if (!isPremium) {
152
+ // return c.json({ error: 'Premium subscription required' }, 403)
153
+ // }
154
+ // } catch {
155
+ // return c.json({ error: 'Invalid authentication token' }, 401)
156
+ // }
157
+ // }
158
+ // try {
159
+ // const audio = await getSpeech({
160
+ // input: input.input,
161
+ // voice: input.voice,
162
+ // speed,
163
+ // volume_multiplier: volumeMultiplier,
164
+ // })
165
+ //
166
+ // const stream =
167
+ // audio.body instanceof Readable
168
+ // ? Readable.toWeb(audio.body)
169
+ // : Readable.toWeb(Readable.from([audio.body]))
170
+ //
171
+ // return new Response(stream as globalThis.ReadableStream<Uint8Array>, {
172
+ // headers: {
173
+ // 'content-type': 'audio/mpeg',
174
+ // 'content-length': String(audio.size),
175
+ // 'cache-control': 'no-store',
176
+ // },
177
+ // })
178
+ // } catch (error) {
179
+ // console.error('Speech synthesis failed:', error)
180
+ //
181
+ // return c.json({ error: 'Speech synthesis failed' }, 502)
182
+ // }
183
+ });
184
+ const server = serve({
185
+ fetch: app.fetch,
186
+ hostname: config.host,
187
+ port: config.port,
188
+ });
189
+ console.log(`
190
+ ##################################################
191
+ # Gemini TTS Proxy #
192
+ ##################################################
193
+ # Server: http://${config.host}:${config.port}
194
+ # Cache: ${config.cacheDir}
195
+ # FFmpeg: ${ffmpegInfo.version}
196
+ ##################################################
197
+ `);
198
+ let shuttingDown = false;
199
+ async function shutdown(signal) {
200
+ if (shuttingDown)
201
+ return;
202
+ shuttingDown = true;
203
+ console.log(`\nReceived ${signal}. Shutting down Gemini TTS Proxy...`);
204
+ server.close();
205
+ try {
206
+ await closeStats();
207
+ console.log('Usage stats flushed successfully.');
208
+ }
209
+ catch (error) {
210
+ console.error('Failed to flush usage stats:', error);
211
+ process.exitCode = 1;
212
+ }
213
+ console.log('Gemini TTS Proxy stopped.');
214
+ }
215
+ process.on('SIGINT', () => void shutdown('SIGINT'));
216
+ process.on('SIGTERM', () => void shutdown('SIGTERM'));
217
+ export { app };
218
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/lib/stats.d.ts ADDED
@@ -0,0 +1,3 @@
1
+ export declare function recordUsage(hash: string): void;
2
+ export declare function closeStats(): Promise<void>;
3
+ //# sourceMappingURL=stats.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.d.ts","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"AAoCA,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAE9C;AAsDD,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAQhD"}
package/lib/stats.js ADDED
@@ -0,0 +1,80 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
3
+ import { dirname, join } from 'node:path';
4
+ import { config } from './config.js';
5
+ const statsPath = join(config.cacheDir, 'stats.json');
6
+ const pending = {};
7
+ let flushPromise;
8
+ async function readStats() {
9
+ try {
10
+ const content = await readFile(statsPath, 'utf8');
11
+ const parsed = JSON.parse(content);
12
+ if (parsed === null ||
13
+ typeof parsed !== 'object' ||
14
+ Array.isArray(parsed)) {
15
+ throw new Error('Invalid stats.json format');
16
+ }
17
+ return parsed;
18
+ }
19
+ catch (error) {
20
+ if (error.code === 'ENOENT') {
21
+ return {};
22
+ }
23
+ throw error;
24
+ }
25
+ }
26
+ export function recordUsage(hash) {
27
+ pending[hash] = (pending[hash] ?? 0) + 1;
28
+ }
29
+ async function flushStats() {
30
+ const snapshot = { ...pending };
31
+ for (const hash of Object.keys(snapshot)) {
32
+ delete pending[hash];
33
+ }
34
+ if (Object.keys(snapshot).length === 0)
35
+ return;
36
+ try {
37
+ await mkdir(dirname(statsPath), { recursive: true });
38
+ const stats = await readStats();
39
+ for (const [hash, count] of Object.entries(snapshot)) {
40
+ stats[hash] = (stats[hash] ?? 0) + count;
41
+ }
42
+ const tempPath = `${statsPath}.${randomUUID()}.tmp`;
43
+ try {
44
+ await writeFile(tempPath, JSON.stringify(stats), 'utf8');
45
+ await rename(tempPath, statsPath);
46
+ }
47
+ catch (error) {
48
+ const { unlink } = await import('node:fs/promises');
49
+ await unlink(tempPath).catch(() => { });
50
+ throw error;
51
+ }
52
+ }
53
+ catch (error) {
54
+ for (const [hash, count] of Object.entries(snapshot)) {
55
+ pending[hash] = (pending[hash] ?? 0) + count;
56
+ }
57
+ throw error;
58
+ }
59
+ }
60
+ function scheduleFlush() {
61
+ if (flushPromise)
62
+ return;
63
+ flushPromise = flushStats()
64
+ .catch((error) => {
65
+ console.error('Failed to persist usage stats:', error);
66
+ })
67
+ .finally(() => {
68
+ flushPromise = undefined;
69
+ });
70
+ }
71
+ const timer = setInterval(scheduleFlush, config.statsFlushIntervalMs);
72
+ timer.unref();
73
+ export async function closeStats() {
74
+ clearInterval(timer);
75
+ if (flushPromise) {
76
+ await flushPromise;
77
+ }
78
+ await flushStats();
79
+ }
80
+ //# sourceMappingURL=stats.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stats.js","sourceRoot":"","sources":["../src/stats.ts"],"names":[],"mappings":"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"}
package/lib/tts.d.ts ADDED
@@ -0,0 +1,31 @@
1
+ import { ReactiveController } from '@snar/lit';
2
+ import { type GeminiTtsVoice, type SpeechOptions } from './core.js';
3
+ export type ConnectionState = 'disconnected' | 'connecting' | 'connection_error' | 'connected';
4
+ export interface GeminiTtsVoiceInfo {
5
+ id: GeminiTtsVoice;
6
+ name: string;
7
+ }
8
+ export interface AudioRequest {
9
+ key: string;
10
+ audio: () => Promise<Blob | AudioBuffer>;
11
+ }
12
+ export declare class GeminiTtsClient extends ReactiveController {
13
+ state: ConnectionState;
14
+ voices: GeminiTtsVoiceInfo[];
15
+ endpoint: string;
16
+ private voicesPromise;
17
+ private cache;
18
+ constructor({ endpoint, }?: {
19
+ endpoint?: string;
20
+ });
21
+ connect({ reconnect }?: {
22
+ reconnect?: boolean;
23
+ }): Promise<void>;
24
+ disconnect(): void;
25
+ getAudio(options: SpeechOptions): Promise<AudioRequest>;
26
+ protected fetchAudio(options: SpeechOptions): Promise<Blob>;
27
+ private fetchVoices;
28
+ private ensureVoices;
29
+ getVoiceTitleFromId(voiceId: GeminiTtsVoice): Promise<string | undefined>;
30
+ }
31
+ //# sourceMappingURL=tts.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tts.d.ts","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,kBAAkB,EAAS,MAAM,WAAW,CAAA;AAErD,OAAO,EAAe,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAA;AAEhF,MAAM,MAAM,eAAe,GAC1B,cAAc,GAAG,YAAY,GAAG,kBAAkB,GAAG,WAAW,CAAA;AAEjE,MAAM,WAAW,kBAAkB;IAClC,EAAE,EAAE,cAAc,CAAA;IAClB,IAAI,EAAE,MAAM,CAAA;CACZ;AAID,MAAM,WAAW,YAAY;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,KAAK,EAAE,MAAM,OAAO,CAAC,IAAI,GAAG,WAAW,CAAC,CAAA;CACxC;AAED,qBAAa,eAAgB,SAAQ,kBAAkB;IAC7C,KAAK,EAAE,eAAe,CAAiB;IACvC,MAAM,EAAE,kBAAkB,EAAE,CAAK;IACjC,QAAQ,EAAE,MAAM,CAAA;IAEzB,OAAO,CAAC,aAAa,CAA2B;IAChD,OAAO,CAAC,KAAK,CAAiC;IAE9C,YAAY,EACX,QAAkC,GAClC,GAAE;QACF,QAAQ,CAAC,EAAE,MAAM,CAAA;KACZ,EAGL;IAEK,OAAO,CAAC,EAAE,SAAiB,EAAE,GAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAA;KAAO,iBAqBhE;IAED,UAAU,SAIT;IAEK,QAAQ,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC,CAkC5D;IAED,UAAgB,UAAU,CAAC,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BhE;YAEa,WAAW;YAiBX,YAAY;IAapB,mBAAmB,CACxB,OAAO,EAAE,cAAc,GACrB,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CAY7B;CACD"}
package/lib/tts.js ADDED
@@ -0,0 +1,134 @@
1
+ var __decorate = (this && this.__decorate) || function (decorators, target, key, desc) {
2
+ var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
3
+ if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
4
+ else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
5
+ return c > 3 && r && Object.defineProperty(target, key, r), r;
6
+ };
7
+ import { ReactiveController, state } from '@snar/lit';
8
+ import { getCacheKey } from './core.js';
9
+ export class GeminiTtsClient extends ReactiveController {
10
+ constructor({ endpoint = 'http://127.0.0.1:3000', } = {}) {
11
+ super();
12
+ this.state = 'disconnected';
13
+ this.voices = [];
14
+ this.cache = new Map();
15
+ this.endpoint = endpoint.replace(/\/+$/, '');
16
+ }
17
+ async connect({ reconnect = false } = {}) {
18
+ if (this.state === 'connected' && !reconnect) {
19
+ return;
20
+ }
21
+ this.state = 'connecting';
22
+ try {
23
+ const response = await fetch(`${this.endpoint}/health`);
24
+ if (!response.ok) {
25
+ throw new Error(`Gemini TTS health check failed: HTTP ${response.status}`);
26
+ }
27
+ this.state = 'connected';
28
+ }
29
+ catch (error) {
30
+ this.state = 'connection_error';
31
+ throw error;
32
+ }
33
+ }
34
+ disconnect() {
35
+ this.voices = [];
36
+ this.voicesPromise = undefined;
37
+ this.state = 'disconnected';
38
+ }
39
+ async getAudio(options) {
40
+ if (this.state !== 'connected') {
41
+ throw new Error('Gemini TTS is not connected');
42
+ }
43
+ const key = await getCacheKey(options);
44
+ const cached = this.cache.get(key);
45
+ if (cached) {
46
+ return {
47
+ key,
48
+ audio: function () {
49
+ return Promise.resolve(cached);
50
+ },
51
+ };
52
+ }
53
+ const audio = this.fetchAudio(options);
54
+ this.cache.set(key, audio);
55
+ try {
56
+ const blob = await audio;
57
+ this.cache.set(key, blob);
58
+ return {
59
+ key,
60
+ audio: async function () {
61
+ return blob;
62
+ },
63
+ };
64
+ }
65
+ catch (error) {
66
+ this.cache.delete(key);
67
+ throw error;
68
+ }
69
+ }
70
+ async fetchAudio(options) {
71
+ const params = new URLSearchParams({
72
+ input: options.input,
73
+ model: options.model,
74
+ voice: options.voice,
75
+ languageCode: options.languageCode,
76
+ });
77
+ if (options.systemInstruction) {
78
+ params.set('systemInstruction', options.systemInstruction);
79
+ }
80
+ const headers = new Headers();
81
+ if (options.apiKey) {
82
+ headers.set('x-goog-api-key', options.apiKey);
83
+ }
84
+ const response = await fetch(`${this.endpoint}/v1/audio/speech?${params}`, {
85
+ headers,
86
+ });
87
+ if (!response.ok) {
88
+ throw new Error(`Gemini TTS synthesis failed: HTTP ${response.status}`);
89
+ }
90
+ return response.blob();
91
+ }
92
+ async fetchVoices() {
93
+ if (this.state !== 'connected') {
94
+ throw new Error('Gemini TTS is not connected');
95
+ }
96
+ const response = await fetch(`${this.endpoint}/v1/voices`);
97
+ if (!response.ok) {
98
+ throw new Error(`Gemini TTS voices request failed: HTTP ${response.status}`);
99
+ }
100
+ const data = await response.json();
101
+ this.voices = data.voices;
102
+ }
103
+ async ensureVoices() {
104
+ if (this.voices.length > 0)
105
+ return;
106
+ if (!this.voicesPromise) {
107
+ this.voicesPromise = this.fetchVoices().catch((error) => {
108
+ this.voicesPromise = undefined;
109
+ throw error;
110
+ });
111
+ }
112
+ await this.voicesPromise;
113
+ }
114
+ async getVoiceTitleFromId(voiceId) {
115
+ if (this.state !== 'connected') {
116
+ throw new Error('Gemini TTS is not connected');
117
+ }
118
+ await this.ensureVoices();
119
+ const voice = this.voices.find((voice) => voice.id === voiceId);
120
+ if (voice) {
121
+ return voice.name;
122
+ }
123
+ }
124
+ }
125
+ __decorate([
126
+ state()
127
+ ], GeminiTtsClient.prototype, "state", void 0);
128
+ __decorate([
129
+ state()
130
+ ], GeminiTtsClient.prototype, "voices", void 0);
131
+ __decorate([
132
+ state()
133
+ ], GeminiTtsClient.prototype, "endpoint", void 0);
134
+ //# sourceMappingURL=tts.js.map
package/lib/tts.js.map ADDED
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tts.js","sourceRoot":"","sources":["../src/tts.ts"],"names":[],"mappings":"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"}