koishi-plugin-chatsound 0.0.3 → 1.0.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/index.d.ts CHANGED
@@ -7,13 +7,14 @@ declare module 'koishi' {
7
7
  }
8
8
  export declare const name = "chatsound";
9
9
  export declare const inject: {
10
- required: string[];
10
+ optional: string[];
11
11
  };
12
12
  export interface Config {
13
13
  soundPath: string[];
14
14
  defaultPitch: number;
15
15
  minPitch: number;
16
16
  maxPitch: number;
17
+ keepSoundLength: boolean;
17
18
  audioType: "mp3" | "silk";
18
19
  }
19
20
  export declare const Config: Schema<Config>;
package/lib/index.js CHANGED
@@ -30,20 +30,62 @@ var import_koishi = require("koishi");
30
30
  var import_promises = require("fs/promises");
31
31
  var import_path = require("path");
32
32
  var import_child_process = require("child_process");
33
- var import_util = require("util");
34
- var execAsync = (0, import_util.promisify)(import_child_process.exec);
35
33
  var name = "chatsound";
36
34
  var inject = {
37
- required: ["silk"]
35
+ optional: ["silk"]
38
36
  };
39
37
  var Config = import_koishi.Schema.object({
40
- soundPath: import_koishi.Schema.array(import_koishi.Schema.string()).description("用于搜索的音频路径"),
38
+ soundPath: import_koishi.Schema.array(import_koishi.Schema.string()).description("用于搜索音频的*绝对*路径,支持搜索mp3 wav ogg flac m4a aac格式"),
41
39
  defaultPitch: import_koishi.Schema.number().default(100).description("默认的语音音调(百分比)"),
42
40
  minPitch: import_koishi.Schema.number().default(80).min(0).description("最小Pitch,不小于0"),
43
41
  maxPitch: import_koishi.Schema.number().default(200).description("最大Pitch"),
42
+ keepSoundLength: import_koishi.Schema.boolean().default(false).description("保持音频播放长度,仅进行 变调"),
44
43
  audioType: import_koishi.Schema.union(["mp3", "silk"]).default("mp3").description("最终发送的类型,QQ及微信选择SILK")
45
44
  });
46
45
  var AUDIO_EXTENSIONS = [".mp3", ".wav", ".ogg", ".flac", ".m4a", ".aac"];
46
+ var ffmpegAvailable = null;
47
+ var ffmpegCheckPromise = null;
48
+ async function checkFFmpeg() {
49
+ if (ffmpegAvailable !== null)
50
+ return ffmpegAvailable;
51
+ if (ffmpegCheckPromise)
52
+ return ffmpegCheckPromise;
53
+ ffmpegCheckPromise = new Promise((resolve) => {
54
+ const { spawn: spawn2 } = require("child_process");
55
+ let resolved = false;
56
+ const timeout = setTimeout(() => {
57
+ if (!resolved) {
58
+ resolved = true;
59
+ resolve(false);
60
+ }
61
+ }, 3e3);
62
+ try {
63
+ const ffmpeg = spawn2("ffmpeg", ["-version"]);
64
+ ffmpeg.on("error", () => {
65
+ if (!resolved) {
66
+ resolved = true;
67
+ clearTimeout(timeout);
68
+ resolve(false);
69
+ }
70
+ });
71
+ ffmpeg.on("close", (code) => {
72
+ if (!resolved) {
73
+ resolved = true;
74
+ clearTimeout(timeout);
75
+ resolve(code === 0);
76
+ }
77
+ });
78
+ } catch {
79
+ clearTimeout(timeout);
80
+ resolve(false);
81
+ }
82
+ }).then((result) => {
83
+ ffmpegAvailable = result;
84
+ return result;
85
+ });
86
+ return ffmpegCheckPromise;
87
+ }
88
+ __name(checkFFmpeg, "checkFFmpeg");
47
89
  async function findAudioFile(trigger, soundPaths) {
48
90
  for (const basePath of soundPaths) {
49
91
  try {
@@ -69,8 +111,6 @@ async function runFFmpeg(commandArgs) {
69
111
  const ffmpeg = (0, import_child_process.spawn)("ffmpeg", commandArgs);
70
112
  const chunks = [];
71
113
  ffmpeg.stdout.on("data", (chunk) => chunks.push(chunk));
72
- ffmpeg.stderr.on("data", (chunk) => {
73
- });
74
114
  ffmpeg.on("close", (code) => {
75
115
  if (code === 0) {
76
116
  resolve(Buffer.concat(chunks));
@@ -82,49 +122,41 @@ async function runFFmpeg(commandArgs) {
82
122
  });
83
123
  }
84
124
  __name(runFFmpeg, "runFFmpeg");
85
- async function applyPitch(inputPath, pitch, audioType) {
125
+ async function applyPitch(inputPath, pitch, audioType, keepSoundLength) {
86
126
  const pitchFactor = pitch / 100;
127
+ let args = [
128
+ "-i",
129
+ inputPath,
130
+ "-vn",
131
+ "-sn",
132
+ "-dn",
133
+ "-y"
134
+ ];
135
+ if (keepSoundLength) {
136
+ args.push("-af", `rubberband=pitch=${pitchFactor}`);
137
+ } else {
138
+ args.push("-af", `asetrate=sample_rate*${pitchFactor}`);
139
+ }
87
140
  if (audioType === "mp3") {
88
- const args = [
89
- "-i",
90
- inputPath,
91
- "-vn",
92
- "-sn",
93
- "-dn",
94
- "-filter:a",
95
- `rubberband=pitch=${pitchFactor}`,
96
- "-f",
97
- "mp3",
98
- "-y",
99
- "pipe:1"
100
- ];
141
+ args.push("-f", "mp3", "pipe:1");
101
142
  const data = await runFFmpeg(args);
102
143
  return { data, mimeType: "audio/mp3" };
103
144
  } else {
104
- const args = [
105
- "-i",
106
- inputPath,
107
- "-vn",
108
- "-sn",
109
- "-dn",
110
- "-filter:a",
111
- `rubberband=pitch=${pitchFactor}`,
112
- "-f",
113
- "s16le",
114
- "-ac",
115
- "1",
116
- "-ar",
117
- "24000",
118
- "-y",
119
- "pipe:1"
120
- ];
145
+ args.push("-f", "s16le", "-ac", "1", "-ar", "24000", "pipe:1");
121
146
  const data = await runFFmpeg(args);
122
147
  return { data, mimeType: "audio/pcm" };
123
148
  }
124
149
  }
125
150
  __name(applyPitch, "applyPitch");
126
151
  function apply(ctx, config) {
127
- ctx.command("v <trigger:string> [pitch:number]").action(async ({ session }, trigger, pitch) => {
152
+ checkFFmpeg().then((available) => {
153
+ if (!available) {
154
+ ctx.logger.warn("FFmpeg not found! Audio processing will fail.");
155
+ } else {
156
+ ctx.logger.debug("FFmpeg is available.");
157
+ }
158
+ });
159
+ ctx.command("v <trigger:string> [pitch:number] 发送一个噪音").action(async ({ session }, trigger, pitch) => {
128
160
  if (!trigger) {
129
161
  return "至少给个名字吧大王";
130
162
  }
@@ -134,10 +166,14 @@ function apply(ctx, config) {
134
166
  return `没有这种音频`;
135
167
  }
136
168
  try {
137
- const { data, mimeType } = await applyPitch(audioPath, actualPitch, config.audioType);
169
+ const { data, mimeType } = await applyPitch(audioPath, actualPitch, config.audioType, config.keepSoundLength);
138
170
  if (config.audioType === "silk") {
139
- const silkResult = await ctx.silk.encode(data, 24e3);
140
- await session.send(import_koishi.h.audio(silkResult.data, "audio/silk"));
171
+ if (ctx.silk) {
172
+ const silkResult = await ctx.silk.encode(data, 24e3);
173
+ await session.send(import_koishi.h.audio(silkResult.data, "audio/silk"));
174
+ } else {
175
+ return "没有安装必要的SILK插件";
176
+ }
141
177
  } else {
142
178
  await session.send(import_koishi.h.audio(data, mimeType));
143
179
  }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "contributors": [
5
5
  "Dr.Abc <me@drabc.net>"
6
6
  ],
7
- "version": "0.0.3",
7
+ "version": "1.0.1",
8
8
  "homepage": "https://github.com/DrAbcOfficial/koishi-plugin-chatsound",
9
9
  "repository": {
10
10
  "type": "git",
@@ -33,7 +33,7 @@
33
33
  "zh": "发送预定的噪音,可以变调,依赖ffmpeg"
34
34
  },
35
35
  "service":{
36
- "required": ["silk"]
36
+ "optional": ["silk"]
37
37
  },
38
38
  "preview": true
39
39
  }