@mekuru/coeiroink 0.1.2 → 0.1.4

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/README.jp.md CHANGED
@@ -11,17 +11,17 @@ Mekuru Movie Maker のセリフを、COEIROINK v2 で読み上げる `Coeiroink`
11
11
  Bun 1.4 以上・libx264 付き ffmpeg・ffprobe・日本語フォントを用意し、プロジェクトに追加します。
12
12
 
13
13
  ```sh
14
- bun add -d @mekuru/core @mekuru/cli @mekuru/coeiroink
14
+ bun add -d @mekuru/types @mekuru/core @mekuru/coeiroink
15
15
  ```
16
16
 
17
- `tsconfig.json` に `jsx: "react-jsx"` と `jsxImportSource: "@mekuru/core"` を設定します。`bun create @mekuru` では設定済みです。
17
+ `tsconfig.json` に `jsx: "react-jsx"` と `jsxImportSource: "@mekuru/types"` を設定します。`bun create @mekuru` では設定済みです。
18
18
 
19
19
  ## 簡単な使い方
20
20
 
21
21
  `src/main.tsx` に保存します。
22
22
 
23
23
  ```tsx
24
- import { writeXml } from "@mekuru/core";
24
+ import { writeXml } from "@mekuru/types";
25
25
  import { Coeiroink } from "@mekuru/coeiroink";
26
26
 
27
27
  const text = "こんにちは。";
@@ -96,7 +96,7 @@ import { Coeiroink, tsukuyomi } from "@mekuru/coeiroink";
96
96
  - `len`: 無音を含む全体のフレーム数。既定: 音声の長さを切り上げた値 + `gap`。話速は変えない。
97
97
  - `id`・`at`: 生成する `<dur>` の識別名と開始時刻。[時間の属性](../../docs/reference/Syntax.jp.md#時間の属性)を参照。
98
98
 
99
- ルビ `|表示《よみ》` は「よみ」を読み、タイムラインには「表示」を残します。半角 `|` と `漢字《かんじ》` も使用できます。`<br />` は空白になり、ほかの要素は入れられません。セリフの連続再生は `<seq>`、字幕との同時表示は `<stack>`、口パクは[立ち絵の `Talk`](../portrait/README.jp.md) を使います。
99
+ ルビ `|表示《よみ》` は「よみ」を読み、タイムラインには「表示」を残します。半角 `|` と `漢字《かんじ》` も使用できます。`<br />` は空白になり、ほかの要素は入れられません。セリフの連続再生は `<seq>`、字幕との同時表示は `<stack>`、口パクは[立ち絵の `Talk`](../narrator/README.jp.md) を使います。
100
100
 
101
101
  ### キャッシュとプレビュー
102
102
 
@@ -113,3 +113,5 @@ curl http://127.0.0.1:50032/v1/speakers
113
113
  ```
114
114
 
115
115
  接続できない場合は起動状態とポートを確認します。Windows のエンジンには [WSL の mirrored ネットワーク](https://learn.microsoft.com/en-us/windows/wsl/networking#mirrored-mode-networking)で `127.0.0.1` から接続できます。
116
+
117
+ 独自のナレータでは `await Coeiroink.prepare({ ...声の設定, children })` で音声素材・表示文字・長さをまとめて取得できます。`id`・`at` は配置する部品に付けます。[共有する音声素材](../speech/README.jp.md#共有する音声素材)と[ナレータの組み立て](../narrator/README.jp.md)を参照してください。
package/README.md CHANGED
@@ -11,17 +11,17 @@ Install [COEIROINK v2](https://coeiroink.com/download), download the desired voi
11
11
  Requires Bun 1.4+, ffmpeg with libx264, ffprobe, and Japanese fonts. Install in the project folder:
12
12
 
13
13
  ```sh
14
- bun add -d @mekuru/core @mekuru/cli @mekuru/coeiroink
14
+ bun add -d @mekuru/types @mekuru/core @mekuru/coeiroink
15
15
  ```
16
16
 
17
- In `tsconfig.json`, set `jsx: "react-jsx"` and `jsxImportSource: "@mekuru/core"` (already set by `bun create @mekuru`).
17
+ In `tsconfig.json`, set `jsx: "react-jsx"` and `jsxImportSource: "@mekuru/types"` (already set by `bun create @mekuru`).
18
18
 
19
19
  ## Quick start
20
20
 
21
21
  Save as `src/main.tsx`:
22
22
 
23
23
  ```tsx
24
- import { writeXml } from "@mekuru/core";
24
+ import { writeXml } from "@mekuru/types";
25
25
  import { Coeiroink } from "@mekuru/coeiroink";
26
26
 
27
27
  const text = "こんにちは。";
@@ -96,7 +96,7 @@ Unknown speakers are reported with the available speaker/style names. Check `/v1
96
96
  - `len`: Total frames, including the gap. Default: rounded-up audio duration + `gap`. Does not change speaking speed.
97
97
  - `id`, `at`: Identifier and start timing for the generated `<dur>`; see [timing](../../docs/reference/Syntax.md#timing-attributes).
98
98
 
99
- Ruby `|表示《よみ》` reads `よみ` and keeps `表示` in the timeline. ASCII `|` and `漢字《かんじ》` also work. `<br />` becomes a space; other nested elements are rejected. Use `<seq>` for successive lines and `<stack>` for speech with subtitles. For lip sync, wrap a line in a [portrait's `Talk`](../portrait/README.md).
99
+ Ruby `|表示《よみ》` reads `よみ` and keeps `表示` in the timeline. ASCII `|` and `漢字《かんじ》` also work. `<br />` becomes a space; other nested elements are rejected. Use `<seq>` for successive lines and `<stack>` for speech with subtitles. For lip sync, wrap a line in a [portrait's `Talk`](../narrator/README.md).
100
100
 
101
101
  ### Cache and preview
102
102
 
@@ -113,3 +113,5 @@ curl http://127.0.0.1:50032/v1/speakers
113
113
  ```
114
114
 
115
115
  For connection errors, check the engine and port. On Windows, use [WSL mirrored networking](https://learn.microsoft.com/en-us/windows/wsl/networking#mirrored-mode-networking) to reach the engine through `127.0.0.1`.
116
+
117
+ For custom narrators, `await Coeiroink.prepare({ ...voiceSettings, children })` returns shared audio, display text, and duration. Put `id` and `at` on the components that place it. See [shared audio sources](../speech/README.md#shared-audio-sources) and [composing narrators](../narrator/README.md).
package/package.json CHANGED
@@ -1,6 +1,12 @@
1
1
  {
2
2
  "name": "@mekuru/coeiroink",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
+ "repository": {
5
+ "type": "git",
6
+ "url": "git+https://github.com/kanade-k-1228/mekuru.git",
7
+ "directory": "pkgs/coeiroink"
8
+ },
9
+ "homepage": "https://github.com/kanade-k-1228/mekuru/blob/main/pkgs/coeiroink/README.md",
4
10
  "description": "The speech component <Coeiroink> of Mekuru Movie Maker: lines of speech read by COEIROINK",
5
11
  "type": "module",
6
12
  "exports": {
@@ -11,13 +17,14 @@
11
17
  ],
12
18
  "sideEffects": false,
13
19
  "peerDependencies": {
14
- "@mekuru/core": "^0.1.2"
20
+ "@mekuru/types": "^0.1.4"
15
21
  },
16
22
  "devDependencies": {
17
- "@mekuru/core": "0.1.2"
23
+ "@mekuru/types": "0.1.4"
18
24
  },
19
25
  "dependencies": {
20
- "@mekuru/speech": "^0.1.2"
26
+ "@mekuru/speech": "^0.1.4",
27
+ "@mekuru/audio": "^0.1.4"
21
28
  },
22
29
  "engines": {
23
30
  "bun": ">=1.4"
package/src/client.ts ADDED
@@ -0,0 +1,98 @@
1
+ import type { SpeechProps } from "@mekuru/speech";
2
+
3
+ import { httpClient, limit } from "@mekuru/speech/engine";
4
+
5
+ import type { CoeiroinkProps } from "./props.ts";
6
+ type Voice = Omit<CoeiroinkProps, keyof SpeechProps | "url">;
7
+
8
+ const DEFAULT_URL = "http://127.0.0.1:50032";
9
+
10
+ const CONCURRENCY = 1;
11
+
12
+ interface Style {
13
+ speaker: string;
14
+ uuid: string;
15
+ style: string;
16
+ id: number;
17
+ }
18
+
19
+ class Engine {
20
+ private readonly limited = limit(CONCURRENCY);
21
+ private styles?: Promise<Style[]>;
22
+
23
+ private readonly call: ReturnType<typeof httpClient>;
24
+
25
+ constructor(url: string) {
26
+ this.call = httpClient(
27
+ url,
28
+ "COEIROINK",
29
+ " - start COEIROINK v2 (its engine listens on port 50032 once the app has started)\n" +
30
+ ' - if the engine is elsewhere, set `url="http://<host>:50032"` on `<Coeiroink>` or the MEKURU_COEIROINK_URL environment variable\n' +
31
+ " - from WSL2, with COEIROINK on Windows: turn on mirrored networking (`networkingMode=mirrored` in .wslconfig)",
32
+ );
33
+ }
34
+
35
+ private async list(again = false): Promise<Style[]> {
36
+ if (again) this.styles = undefined;
37
+ this.styles ??= this.call("/v1/speakers").then(async (res) => {
38
+ const speakers = (await res.json()) as { speakerName: string; speakerUuid: string; styles: { styleName: string; styleId: number }[] }[];
39
+ return speakers.flatMap((s) => s.styles.map((st) => ({ speaker: s.speakerName, uuid: s.speakerUuid, style: st.styleName, id: st.styleId })));
40
+ });
41
+ return this.styles.catch((e: unknown) => {
42
+ this.styles = undefined;
43
+ throw e;
44
+ });
45
+ }
46
+
47
+ private async style(speaker: number | string): Promise<Style> {
48
+ const [name, style] = String(speaker)
49
+ .split("/")
50
+ .map((s) => s.trim());
51
+ const id = style === undefined && /^\d+$/.test(name) ? Number(name) : undefined;
52
+ const pick = (styles: Style[]) =>
53
+ styles.find((s) => (id !== undefined ? s.id === id : (s.speaker === name || s.uuid === name) && (style === undefined || s.style === style)));
54
+ const found = pick(await this.list()) ?? pick(await this.list(true));
55
+ if (found) return found;
56
+ const known = (await this.list()).map((s) => `${s.speaker}/${s.style}`).join(", ");
57
+ throw new Error(
58
+ `COEIROINK has no speaker \`${speaker}\` (it has ${known || "none"}).\n` +
59
+ " More characters and styles are downloaded in COEIROINK (キャラクターダウンロード), which is then restarted.",
60
+ );
61
+ }
62
+
63
+ async synthesize(props: Voice, text: string, out: string): Promise<void> {
64
+ return this.limited(async () => {
65
+ const { uuid, id } = await this.style(props.speaker);
66
+ const wav = await this.call("/v1/synthesis", {
67
+ method: "POST",
68
+ headers: { "Content-Type": "application/json" },
69
+ body: JSON.stringify({
70
+ speakerUuid: uuid,
71
+ styleId: id,
72
+ text,
73
+ prosodyDetail: [],
74
+ speedScale: props.speed ?? 1,
75
+ pitchScale: props.pitch ?? 0,
76
+ intonationScale: props.intonation ?? 1,
77
+ volumeScale: props.volume ?? 1,
78
+ prePhonemeLength: props.pre ?? 0.1,
79
+ postPhonemeLength: props.post ?? 0.1,
80
+ pauseLength: props.pause,
81
+ outputSamplingRate: 48000,
82
+ }),
83
+ });
84
+ await Bun.write(out, Buffer.from(await wav.arrayBuffer()));
85
+ });
86
+ }
87
+ }
88
+
89
+ const engines = new Map<string, Engine>();
90
+
91
+ export const engine = (url: string | undefined): Engine => {
92
+ const at = (url ?? process.env.MEKURU_COEIROINK_URL ?? DEFAULT_URL).replace(/\/+$/, "");
93
+ const found = engines.get(at);
94
+ if (found) return found;
95
+ const created = new Engine(at);
96
+ engines.set(at, created);
97
+ return created;
98
+ };
package/src/index.tsx CHANGED
@@ -1,114 +1,12 @@
1
- import type { JSX } from "@mekuru/core";
2
- import { httpClient, limit } from "@mekuru/speech/engine";
3
- import { line, voiceOf, type SpeechProps } from "@mekuru/speech";
4
-
5
- const DEFAULT_URL = "http://127.0.0.1:50032";
6
-
7
- export interface CoeiroinkProps extends SpeechProps {
8
- speaker: number | string;
9
- speed?: number;
10
- pitch?: number;
11
- intonation?: number;
12
- volume?: number;
13
- pre?: number;
14
- post?: number;
15
- pause?: number;
16
- url?: string;
17
- }
18
-
19
- const CONCURRENCY = 1;
20
-
21
- interface Style {
22
- speaker: string;
23
- uuid: string;
24
- style: string;
25
- id: number;
26
- }
27
-
28
- class Engine {
29
- private readonly limited = limit(CONCURRENCY);
30
- private styles?: Promise<Style[]>;
31
-
32
- private readonly call: ReturnType<typeof httpClient>;
33
-
34
- constructor(url: string) {
35
- this.call = httpClient(
36
- url,
37
- "COEIROINK",
38
- " - start COEIROINK v2 (its engine listens on port 50032 once the app has started)\n" +
39
- ' - if the engine is elsewhere, set `url="http://<host>:50032"` on `<Coeiroink>` or the MEKURU_COEIROINK_URL environment variable\n' +
40
- " - from WSL2, with COEIROINK on Windows: turn on mirrored networking (`networkingMode=mirrored` in .wslconfig)",
41
- );
42
- }
43
-
44
- private async list(again = false): Promise<Style[]> {
45
- if (again) this.styles = undefined;
46
- this.styles ??= this.call("/v1/speakers").then(async (res) => {
47
- const speakers = (await res.json()) as { speakerName: string; speakerUuid: string; styles: { styleName: string; styleId: number }[] }[];
48
- return speakers.flatMap((s) => s.styles.map((st) => ({ speaker: s.speakerName, uuid: s.speakerUuid, style: st.styleName, id: st.styleId })));
49
- });
50
- return this.styles.catch((e: unknown) => {
51
- this.styles = undefined;
52
- throw e;
53
- });
54
- }
55
-
56
- private async style(speaker: number | string): Promise<Style> {
57
- const [name, style] = String(speaker)
58
- .split("/")
59
- .map((s) => s.trim());
60
- const id = style === undefined && /^\d+$/.test(name) ? Number(name) : undefined;
61
- const pick = (styles: Style[]) =>
62
- styles.find((s) => (id !== undefined ? s.id === id : (s.speaker === name || s.uuid === name) && (style === undefined || s.style === style)));
63
- const found = pick(await this.list()) ?? pick(await this.list(true));
64
- if (found) return found;
65
- const known = (await this.list()).map((s) => `${s.speaker}/${s.style}`).join(", ");
66
- throw new Error(
67
- `COEIROINK has no speaker \`${speaker}\` (it has ${known || "none"}).\n` +
68
- " More characters and styles are downloaded in COEIROINK (キャラクターダウンロード), which is then restarted.",
69
- );
70
- }
71
-
72
- async synthesize(props: CoeiroinkProps, text: string, out: string): Promise<void> {
73
- return this.limited(async () => {
74
- const { uuid, id } = await this.style(props.speaker);
75
- const wav = await this.call("/v1/synthesis", {
76
- method: "POST",
77
- headers: { "Content-Type": "application/json" },
78
- body: JSON.stringify({
79
- speakerUuid: uuid,
80
- styleId: id,
81
- text,
82
- prosodyDetail: [],
83
- speedScale: props.speed ?? 1,
84
- pitchScale: props.pitch ?? 0,
85
- intonationScale: props.intonation ?? 1,
86
- volumeScale: props.volume ?? 1,
87
- prePhonemeLength: props.pre ?? 0.1,
88
- postPhonemeLength: props.post ?? 0.1,
89
- pauseLength: props.pause,
90
- outputSamplingRate: 48000,
91
- }),
92
- });
93
- await Bun.write(out, Buffer.from(await wav.arrayBuffer()));
94
- });
95
- }
96
- }
97
-
98
- const engines = new Map<string, Engine>();
99
-
100
- const engine = (url: string | undefined): Engine => {
101
- const at = (url ?? process.env.MEKURU_COEIROINK_URL ?? DEFAULT_URL).replace(/\/+$/, "");
102
- const found = engines.get(at);
103
- if (found) return found;
104
- const created = new Engine(at);
105
- engines.set(at, created);
106
- return created;
107
- };
108
-
109
- export const Coeiroink = async (props: CoeiroinkProps): Promise<Awaited<JSX.Element>> => {
1
+ import { defineSpeech, prepare, voiceOf } from "@mekuru/speech";
2
+ import { engine } from "./client.ts";
3
+ import type { CoeiroinkProps } from "./props.ts";
4
+ export type { CoeiroinkProps } from "./props.ts";
5
+ const prepareLine = async (props: Omit<CoeiroinkProps, "id" | "at">) => {
110
6
  const { url, ...voice } = voiceOf(props);
111
- return line("coeiroink", props, String(props.speaker), voice, (say, out) => engine(url).synthesize(props, say, out));
7
+ return prepare("coeiroink", props, String(props.speaker), voice, (say, out) => engine(url).synthesize(voice, say, out));
112
8
  };
113
9
 
10
+ export const Coeiroink = defineSpeech<CoeiroinkProps>(prepareLine);
11
+
114
12
  export * from "./characters.ts";
package/src/props.ts ADDED
@@ -0,0 +1,12 @@
1
+ import type { SpeechProps } from "@mekuru/speech";
2
+ export interface CoeiroinkProps extends SpeechProps {
3
+ speaker: number | string;
4
+ speed?: number;
5
+ pitch?: number;
6
+ intonation?: number;
7
+ volume?: number;
8
+ pre?: number;
9
+ post?: number;
10
+ pause?: number;
11
+ url?: string;
12
+ }