119 lines
3.4 KiB
TypeScript
119 lines
3.4 KiB
TypeScript
import { createHmac } from "crypto";
|
|
import { join } from "path";
|
|
import fetch from "../utils/fetch";
|
|
import TTSModelBase from ".";
|
|
|
|
export class TTSPapagoModel extends TTSModelBase<TTSPapagoModel.RequestId> {
|
|
protected cachedVoice: Map<string, Promise<Buffer>>;
|
|
constructor() {
|
|
super();
|
|
this.cachedVoice = new Map();
|
|
}
|
|
ttsify(input: string): string {
|
|
return super.ttsify(input);
|
|
}
|
|
public getVoicePath(id: TTSPapagoModel.RequestId): string {
|
|
const audioFileName = TTSModelBase.hashAudioFile(
|
|
id.text,
|
|
`.${id.speaker}.${id.speed.replace(/-/g, "_")}`,
|
|
);
|
|
const audioPath = join(TTSPapagoModel.PapagoAudioCachePath, audioFileName);
|
|
return audioPath;
|
|
}
|
|
async getVoiceBuffer(
|
|
id: TTSPapagoModel.RequestId,
|
|
voiceId?: string,
|
|
): Promise<ArrayBuffer> {
|
|
voiceId ??= await TTSPapagoModel.getVoiceId(id);
|
|
const response = await fetch(
|
|
`https://papago.naver.com/apis/tts/${voiceId}`,
|
|
);
|
|
return await response.arrayBuffer();
|
|
}
|
|
createRequestId(
|
|
text: string,
|
|
speaker?: string,
|
|
speed?: string,
|
|
): TTSPapagoModel.RequestId {
|
|
return {
|
|
text,
|
|
speed: speed ?? "-1",
|
|
speaker: speaker ?? "kyuri",
|
|
};
|
|
}
|
|
}
|
|
export namespace TTSPapagoModel {
|
|
export const instance = new TTSPapagoModel();
|
|
export type RequestId = {
|
|
speaker: string;
|
|
speed: string;
|
|
text: string;
|
|
};
|
|
export const GenerateTokenKey = "v1.9.3_3bdf0438a8";
|
|
export function hmacMD5(key: string, plaintext: string) {
|
|
const hmac = createHmac("md5", key);
|
|
const data = hmac.update(plaintext);
|
|
|
|
return data.digest("base64");
|
|
}
|
|
export function generateToken(time: number) {
|
|
const e = "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, (e) => {
|
|
const t = ((time + 16 * Math.random()) % 16) | 0;
|
|
return (
|
|
(time = Math.floor(time / 16)),
|
|
("x" === e ? t : (3 & t) | 8).toString(16)
|
|
);
|
|
});
|
|
|
|
const plain = `${e}\n${"https://papago.naver.com/apis/tts/makeID"}\n${time}`;
|
|
|
|
return `PPG ${e}:${hmacMD5(GenerateTokenKey, plain)}`;
|
|
}
|
|
export async function getVoiceId(id: RequestId): Promise<string> {
|
|
const input = {
|
|
alpha: "0",
|
|
pitch: "0",
|
|
speaker: id.speaker,
|
|
speed: id.speed,
|
|
text: id.text,
|
|
};
|
|
|
|
const time = new Date().getTime();
|
|
const token = TTSPapagoModel.generateToken(time);
|
|
|
|
const reqbody = new URLSearchParams(Object.entries(input)).toString();
|
|
const response = await fetch("https://papago.naver.com/apis/tts/makeID", {
|
|
headers: {
|
|
"User-Agent":
|
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:121.0) Gecko/20100101 Firefox/121.0",
|
|
Accept: "application/json",
|
|
"Accept-Language": "en",
|
|
"Sec-GPC": "1",
|
|
"Sec-Fetch-Dest": "empty",
|
|
"Sec-Fetch-Mode": "no-cors",
|
|
"Sec-Fetch-Site": "same-origin",
|
|
"Content-Type": "application/x-www-form-urlencoded; charset=UTF-8",
|
|
Authorization: token,
|
|
Timestamp: time.toString(),
|
|
Pragma: "no-cache",
|
|
"Cache-Control": "no-cache",
|
|
},
|
|
referrer: "https://papago.naver.com/",
|
|
body: reqbody,
|
|
method: "POST",
|
|
});
|
|
|
|
if (!response.ok) {
|
|
throw new Error(
|
|
`TTS makeID request failed: ${response.status}: ${await response.text()}`,
|
|
);
|
|
}
|
|
|
|
return ((await response.json()) as any).id;
|
|
}
|
|
export const PapagoAudioCachePath = join(
|
|
TTSModelBase.AudioCachePath,
|
|
"papago",
|
|
);
|
|
}
|
|
export default TTSPapagoModel;
|