yaejunyang/packages/tts/papago.ts

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;