koishi-plugin-chatsound 1.0.0 → 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
@@ -14,6 +14,7 @@ export interface Config {
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
@@ -39,9 +39,53 @@ var Config = import_koishi.Schema.object({
39
39
  defaultPitch: import_koishi.Schema.number().default(100).description("默认的语音音调(百分比)"),
40
40
  minPitch: import_koishi.Schema.number().default(80).min(0).description("最小Pitch,不小于0"),
41
41
  maxPitch: import_koishi.Schema.number().default(200).description("最大Pitch"),
42
+ keepSoundLength: import_koishi.Schema.boolean().default(false).description("保持音频播放长度,仅进行 变调"),
42
43
  audioType: import_koishi.Schema.union(["mp3", "silk"]).default("mp3").description("最终发送的类型,QQ及微信选择SILK")
43
44
  });
44
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");
45
89
  async function findAudioFile(trigger, soundPaths) {
46
90
  for (const basePath of soundPaths) {
47
91
  try {
@@ -78,49 +122,41 @@ async function runFFmpeg(commandArgs) {
78
122
  });
79
123
  }
80
124
  __name(runFFmpeg, "runFFmpeg");
81
- async function applyPitch(inputPath, pitch, audioType) {
125
+ async function applyPitch(inputPath, pitch, audioType, keepSoundLength) {
82
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
+ }
83
140
  if (audioType === "mp3") {
84
- const args = [
85
- "-i",
86
- inputPath,
87
- "-vn",
88
- "-sn",
89
- "-dn",
90
- "-filter:a",
91
- `rubberband=pitch=${pitchFactor}`,
92
- "-f",
93
- "mp3",
94
- "-y",
95
- "pipe:1"
96
- ];
141
+ args.push("-f", "mp3", "pipe:1");
97
142
  const data = await runFFmpeg(args);
98
143
  return { data, mimeType: "audio/mp3" };
99
144
  } else {
100
- const args = [
101
- "-i",
102
- inputPath,
103
- "-vn",
104
- "-sn",
105
- "-dn",
106
- "-filter:a",
107
- `rubberband=pitch=${pitchFactor}`,
108
- "-f",
109
- "s16le",
110
- "-ac",
111
- "1",
112
- "-ar",
113
- "24000",
114
- "-y",
115
- "pipe:1"
116
- ];
145
+ args.push("-f", "s16le", "-ac", "1", "-ar", "24000", "pipe:1");
117
146
  const data = await runFFmpeg(args);
118
147
  return { data, mimeType: "audio/pcm" };
119
148
  }
120
149
  }
121
150
  __name(applyPitch, "applyPitch");
122
151
  function apply(ctx, config) {
123
- 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) => {
124
160
  if (!trigger) {
125
161
  return "至少给个名字吧大王";
126
162
  }
@@ -130,7 +166,7 @@ function apply(ctx, config) {
130
166
  return `没有这种音频`;
131
167
  }
132
168
  try {
133
- const { data, mimeType } = await applyPitch(audioPath, actualPitch, config.audioType);
169
+ const { data, mimeType } = await applyPitch(audioPath, actualPitch, config.audioType, config.keepSoundLength);
134
170
  if (config.audioType === "silk") {
135
171
  if (ctx.silk) {
136
172
  const silkResult = await ctx.silk.encode(data, 24e3);
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "contributors": [
5
5
  "Dr.Abc <me@drabc.net>"
6
6
  ],
7
- "version": "1.0.0",
7
+ "version": "1.0.1",
8
8
  "homepage": "https://github.com/DrAbcOfficial/koishi-plugin-chatsound",
9
9
  "repository": {
10
10
  "type": "git",