107 lines
3.8 KiB
TypeScript
107 lines
3.8 KiB
TypeScript
import { createHmac } from "crypto";
|
|
import { join } from "path";
|
|
import fetch from "../utils/fetch";
|
|
import TTSModelBase from ".";
|
|
import { saferKorean } from "../utils/saferKorean";
|
|
|
|
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(saferKorean(
|
|
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) => {
|
|
var 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;
|