n8n-nodes-piapi
Version:
Community n8n nodes for PiAPI - integrate generative AI capabilities (image, video, audio, 3D) into your workflows
276 lines • 9.98 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.KlingLipSync = void 0;
exports.loadTtsVoices = loadTtsVoices;
exports.loadTtsEmotions = loadTtsEmotions;
const GenericFunctions_1 = require("../shared/GenericFunctions");
const VOICE_EMOTIONS_MAP = {
"Rock": {
name: "Rock",
emotions: ["Happy", "Sad", "Angry", "Neutral"]
},
"Funny": {
name: "Funny",
emotions: ["Happy", "Excited", "Neutral"]
},
"Gentle": {
name: "Gentle",
emotions: ["Calm", "Sad", "Neutral"]
},
};
async function loadTtsVoices() {
try {
const response = await this.helpers.request({
method: 'GET',
url: 'https://klingai.com/api/lip/sync/ttsList?type=',
json: true,
});
if (!response || !Array.isArray(response)) {
return Object.keys(VOICE_EMOTIONS_MAP).map(voice => ({
name: voice,
value: voice,
}));
}
return response.map((voice) => ({
name: voice,
value: voice,
}));
}
catch (error) {
return Object.keys(VOICE_EMOTIONS_MAP).map(voice => ({
name: voice,
value: voice,
}));
}
}
async function loadTtsEmotions() {
const currentVoice = this.getCurrentNodeParameter('ttsVoice');
if (!currentVoice || !VOICE_EMOTIONS_MAP[currentVoice]) {
return [{ name: 'Neutral', value: 'Neutral' }];
}
const emotions = VOICE_EMOTIONS_MAP[currentVoice].emotions;
return emotions.map(emotion => ({
name: emotion,
value: emotion
}));
}
class KlingLipSync {
constructor() {
this.methods = {
loadOptions: {
loadTtsVoices,
loadTtsEmotions,
},
};
this.description = {
displayName: 'PiAPI Kling Lip Sync',
name: 'klingLipSync',
icon: 'file:../piapi.svg',
group: ['transform'],
version: 1,
description: 'Apply lip sync to a Kling video with text-to-speech or audio',
defaults: {
name: 'Kling Lip Sync',
},
inputs: ["main"],
outputs: ["main"],
credentials: [
{
name: 'piAPIApi',
required: true,
},
],
properties: [
{
displayName: 'Original Task ID',
name: 'originTaskId',
type: 'string',
default: '',
required: true,
description: 'The task ID of the original Kling video',
},
{
displayName: 'Audio Source',
name: 'audioSource',
type: 'options',
options: [
{
name: 'Text-to-Speech',
value: 'tts',
description: 'Generate audio from text using TTS',
},
{
name: 'Audio URL',
value: 'audioUrl',
description: 'Use existing audio file from URL',
},
],
default: 'tts',
description: 'Source of audio for lip syncing',
},
{
displayName: 'TTS Text',
name: 'ttsText',
type: 'string',
typeOptions: {
rows: 4,
},
default: '',
required: true,
displayOptions: {
show: {
audioSource: ['tts'],
},
},
description: 'Text to convert to speech',
},
{
displayName: 'Voice',
name: 'ttsVoice',
type: 'options',
typeOptions: {
loadOptionsMethod: 'loadTtsVoices',
},
default: 'Rock',
required: true,
displayOptions: {
show: {
audioSource: ['tts'],
},
},
description: 'Voice to use for text-to-speech',
},
{
displayName: 'Emotion',
name: 'ttsEmotion',
type: 'options',
typeOptions: {
loadOptionsMethod: 'loadTtsEmotions',
},
default: 'Neutral',
required: false,
displayOptions: {
show: {
audioSource: ['tts'],
},
},
description: 'Emotional style for the selected voice',
},
{
displayName: 'Speech Speed',
name: 'ttsSpeed',
type: 'number',
typeOptions: {
minValue: 0.8,
maxValue: 2,
numberPrecision: 1,
},
default: 1,
displayOptions: {
show: {
audioSource: ['tts'],
},
},
description: 'Speed of the speech (0.8-2)',
},
{
displayName: 'Audio URL',
name: 'audioUrl',
type: 'string',
default: '',
required: true,
displayOptions: {
show: {
audioSource: ['audioUrl'],
},
},
description: 'URL to your audio file (mp3, wav, flac, ogg)',
},
{
displayName: 'Wait for Completion',
name: 'waitForCompletion',
type: 'boolean',
default: false,
description: 'Whether to wait for the task to complete before returning',
},
],
};
}
async execute() {
const items = this.getInputData();
const returnData = [];
for (let i = 0; i < items.length; i++) {
try {
const originTaskId = this.getNodeParameter('originTaskId', i);
const audioSource = this.getNodeParameter('audioSource', i);
const waitForCompletion = this.getNodeParameter('waitForCompletion', i, false);
const body = {
model: 'kling',
task_type: 'lip_sync',
input: {
origin_task_id: originTaskId,
},
config: {
webhook_config: {
endpoint: '',
secret: '',
},
},
};
if (audioSource === 'tts') {
const ttsText = this.getNodeParameter('ttsText', i);
const ttsVoice = this.getNodeParameter('ttsVoice', i);
const ttsEmotion = this.getNodeParameter('ttsEmotion', i, 'Neutral');
const ttsSpeed = this.getNodeParameter('ttsSpeed', i);
let voiceWithEmotion = ttsVoice;
if (ttsEmotion && ttsEmotion !== 'Neutral') {
voiceWithEmotion = `${ttsVoice}-${ttsEmotion}`;
}
body.input.tts_text = ttsText;
body.input.tts_timbre = voiceWithEmotion;
body.input.tts_speed = ttsSpeed;
body.input.local_dubbing_url = '';
}
else {
const audioUrl = this.getNodeParameter('audioUrl', i);
try {
new URL(audioUrl);
}
catch (error) {
throw new Error(`Invalid audio URL: ${error.message}`);
}
body.input.tts_text = '';
body.input.tts_timbre = '';
body.input.tts_speed = 1;
body.input.local_dubbing_url = audioUrl;
}
const response = await GenericFunctions_1.piApiRequest.call(this, 'POST', '/api/v1/task', body);
if (response.code !== 200) {
throw new Error(`API Error: ${response.message}`);
}
const taskId = response.data.task_id;
let taskData = response.data;
if (waitForCompletion) {
taskData = await GenericFunctions_1.waitForTaskCompletion.call(this, taskId);
}
returnData.push({
json: taskData,
});
}
catch (error) {
if (this.continueOnFail()) {
returnData.push({
json: {
error: error.message,
},
});
continue;
}
throw error;
}
}
return [returnData];
}
}
exports.KlingLipSync = KlingLipSync;
//# sourceMappingURL=KlingLipSync.node.js.map
;