yaejunyang/packages/tts/papago.ts
2026-02-09 18:31:56 +00:00

101 lines
3.6 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();
}
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;