import { createHmac } from "crypto"; import { join } from "path"; import fetch from "../utils/fetch"; import TTSModelBase from "."; export class TTSPapagoModel extends TTSModelBase { protected cachedVoice: Map> 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 { 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 { 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;