@kmchai/cortico-world-voice 0.1.0 → 0.2.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/src/sing.ts DELETED
@@ -1,73 +0,0 @@
1
- /**
2
- * 唱歌插件的 HTTP 连接。协议跟现有的 DiffSinger World 一致:
3
- * `GET /health`、`POST /sing {lyrics,…}` → `audio/wav`(或 `{audioBase64}`)。
4
- * 只经 HTTP 相连,不 import 对方的包。
5
- */
6
- import type { TtsAudio } from './types.ts';
7
- import { errorSnippet, mimeOf, timedFetch } from './backends/http.ts';
8
-
9
- /** 正文以 `【唱】`(或 `[唱]` / `[sing]`)开头时交给唱歌插件。 */
10
- export const SING_MARK = /^\s*[【\[]\s*(?:唱|sing)\s*[】\]]\s*/i;
11
-
12
- export function singRequestOf(text: string): { lyrics: string; marked: boolean } {
13
- const m = SING_MARK.exec(text);
14
- if (!m) return { lyrics: text.trim(), marked: false };
15
- return { lyrics: text.slice(m[0].length).trim(), marked: true };
16
- }
17
-
18
- function singUrlOf(base: string): string {
19
- const trimmed = base.trim().replace(/\/+$/, '');
20
- const url = new URL(trimmed);
21
- if (!/\/sing$/.test(url.pathname)) url.pathname = `${url.pathname.replace(/\/$/, '')}/sing`;
22
- return url.toString();
23
- }
24
-
25
- export interface SingOptions {
26
- url: string;
27
- token?: string;
28
- lyrics: string;
29
- timeoutMs: number;
30
- fetch: typeof fetch;
31
- signal?: AbortSignal;
32
- }
33
-
34
- export async function singRemote(opts: SingOptions): Promise<TtsAudio> {
35
- const headers: Record<string, string> = { 'Content-Type': 'application/json' };
36
- if (opts.token) headers.Authorization = `Bearer ${opts.token}`;
37
- const res = await timedFetch(opts.fetch, singUrlOf(opts.url), {
38
- method: 'POST',
39
- headers,
40
- body: JSON.stringify({ lyrics: opts.lyrics }),
41
- }, Math.max(30_000, opts.timeoutMs), opts.signal);
42
- if (!res.ok) throw new Error(await errorSnippet(res));
43
- const type = (res.headers.get('content-type') ?? '').toLowerCase();
44
- if (type.startsWith('audio/') || type.includes('octet-stream')) {
45
- const bytes = new Uint8Array(await res.arrayBuffer());
46
- if (!bytes.length) throw new Error('singing service returned an empty body');
47
- return { bytes, mime: type.split(';')[0] || mimeOf('wav') };
48
- }
49
- const data = await res.json().catch(() => null) as { audioBase64?: unknown; mime?: unknown } | null;
50
- if (typeof data?.audioBase64 === 'string' && data.audioBase64) {
51
- return {
52
- bytes: new Uint8Array(Buffer.from(data.audioBase64, 'base64')),
53
- mime: typeof data.mime === 'string' ? data.mime : mimeOf('wav'),
54
- };
55
- }
56
- throw new Error('singing service returned no audio');
57
- }
58
-
59
- export async function singHealth(opts: { url: string; token?: string; fetch: typeof fetch }): Promise<boolean> {
60
- if (!opts.url.trim()) return false;
61
- try {
62
- const url = new URL(opts.url.trim().replace(/\/+$/, ''));
63
- url.pathname = '/health';
64
- const headers: Record<string, string> = {};
65
- if (opts.token) headers.Authorization = `Bearer ${opts.token}`;
66
- const res = await timedFetch(opts.fetch, url.toString(), { method: 'GET', headers }, 5000);
67
- if (!res.ok) return false;
68
- const data = await res.json().catch(() => null) as { ready?: unknown } | null;
69
- return data?.ready !== false;
70
- } catch {
71
- return false;
72
- }
73
- }