[mirotalksfu] - fix avatars and voices

هذا الالتزام موجود في:
Miroslav Pejic
2024-11-18 09:19:36 +01:00
الأصل 0dd7a8a85d
التزام 54e706f454
4 ملفات معدلة مع 93 إضافات و91 حذوفات

عرض الملف

@@ -55,7 +55,7 @@ dev dependencies: {
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.6.38 * @version 1.6.39
* *
*/ */
@@ -2246,13 +2246,13 @@ function startServer() {
} }
}); });
// https://docs.heygen.com/reference/avatar-list // https://docs.heygen.com/reference/list-avatars-v2
socket.on('getAvatarList', async ({}, cb) => { socket.on('getAvatarList', async ({}, cb) => {
if (!config.videoAI.enabled || !config.videoAI.apiKey) if (!config.videoAI.enabled || !config.videoAI.apiKey)
return cb({ error: 'Video AI seems disabled, try later!' }); return cb({ error: 'Video AI seems disabled, try later!' });
try { try {
const response = await axios.get(`${config.videoAI.basePath}/v1/avatar.list`, { const response = await axios.get(`${config.videoAI.basePath}/v2/avatars`, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Api-Key': config.videoAI.apiKey, 'X-Api-Key': config.videoAI.apiKey,
@@ -2270,13 +2270,13 @@ function startServer() {
} }
}); });
// https://docs.heygen.com/reference/get-voices // https://docs.heygen.com/reference/list-voices-v2
socket.on('getVoiceList', async ({}, cb) => { socket.on('getVoiceList', async ({}, cb) => {
if (!config.videoAI.enabled || !config.videoAI.apiKey) if (!config.videoAI.enabled || !config.videoAI.apiKey)
return cb({ error: 'Video AI seems disabled, try later!' }); return cb({ error: 'Video AI seems disabled, try later!' });
try { try {
const response = await axios.get(`${config.videoAI.basePath}/v1/voice.list`, { const response = await axios.get(`${config.videoAI.basePath}/v2/voices`, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'X-Api-Key': config.videoAI.apiKey, 'X-Api-Key': config.videoAI.apiKey,

عرض الملف

@@ -1,6 +1,6 @@
{ {
"name": "mirotalksfu", "name": "mirotalksfu",
"version": "1.6.38", "version": "1.6.39",
"description": "WebRTC SFU browser-based video calls", "description": "WebRTC SFU browser-based video calls",
"main": "Server.js", "main": "Server.js",
"scripts": { "scripts": {
@@ -79,7 +79,7 @@
"ngrok": "^5.0.0-beta.2", "ngrok": "^5.0.0-beta.2",
"nodemailer": "^6.9.16", "nodemailer": "^6.9.16",
"openai": "^4.72.0", "openai": "^4.72.0",
"qs": "6.13.0", "qs": "6.13.1",
"socket.io": "4.8.1", "socket.io": "4.8.1",
"swagger-ui-express": "5.0.1", "swagger-ui-express": "5.0.1",
"uuid": "11.0.3" "uuid": "11.0.3"

عرض الملف

@@ -11,7 +11,7 @@ if (location.href.substr(0, 5) !== 'https') location.href = 'https' + location.h
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.6.38 * @version 1.6.39
* *
*/ */
@@ -4616,7 +4616,7 @@ function showAbout() {
imageUrl: image.about, imageUrl: image.about,
customClass: { image: 'img-about' }, customClass: { image: 'img-about' },
position: 'center', position: 'center',
title: 'WebRTC SFU v1.6.38', title: 'WebRTC SFU v1.6.39',
html: ` html: `
<br /> <br />
<div id="about"> <div id="about">

عرض الملف

@@ -9,7 +9,7 @@
* @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon * @license For commercial or closed source, contact us at license.mirotalk@gmail.com or purchase directly via CodeCanyon
* @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970 * @license CodeCanyon: https://codecanyon.net/item/mirotalk-sfu-webrtc-realtime-video-conferences/40769970
* @author Miroslav Pejic - miroslav.pejic.85@gmail.com * @author Miroslav Pejic - miroslav.pejic.85@gmail.com
* @version 1.6.38 * @version 1.6.39
* *
*/ */
@@ -8330,75 +8330,65 @@ class RoomClient {
//console.log('AVATARS LISTS', completion.response.avatars); //console.log('AVATARS LISTS', completion.response.avatars);
completion.response.avatars.forEach((avatar) => { completion.response.avatars.forEach((avatar) => {
avatar.avatar_states.forEach((avatarUi) => { if (
if ( !excludedIds.includes(avatar.avatar_id) &&
!excludedIds.includes(avatarUi.id) && (showFreeAvatars ? freeAvatars.includes(avatar.avatar_name) : true)
(showFreeAvatars ? freeAvatars.includes(avatarUi.pose_name) : true) ) {
) { const div = document.createElement('div');
const div = document.createElement('div'); div.style.float = 'left';
div.style.float = 'left'; div.style.padding = '5px';
div.style.padding = '5px'; div.style.width = '100px';
div.style.width = '100px'; div.style.height = '200px';
div.style.height = '200px'; const img = document.createElement('img');
const img = document.createElement('img'); const hr = document.createElement('hr');
const hr = document.createElement('hr'); const label = document.createElement('label');
const label = document.createElement('label'); const textContent = document.createTextNode(avatar.avatar_name);
const textContent = document.createTextNode(avatarUi.pose_name); label.appendChild(textContent);
label.appendChild(textContent); //label.style.fontSize = '12px';
//label.style.fontSize = '12px'; img.setAttribute('id', avatar.avatar_id);
img.setAttribute('id', avatarUi.id); img.setAttribute('class', 'avatarImg');
img.setAttribute('class', 'avatarImg'); img.setAttribute('src', avatar.preview_image_url);
img.setAttribute('src', avatarUi.normal_thumbnail_medium); img.setAttribute('width', '100%');
img.setAttribute('width', '100%'); img.setAttribute('height', 'auto');
img.setAttribute('height', 'auto'); img.setAttribute('alt', avatar.avatar_name);
img.setAttribute('alt', avatarUi.pose_name); img.setAttribute('style', 'cursor:pointer; padding: 2px; border-radius: 5px;');
img.setAttribute('style', 'cursor:pointer; padding: 2px; border-radius: 5px;'); img.setAttribute(
img.setAttribute( 'avatarData',
'avatarData', avatar.avatar_id + '|' + avatar.avatar_name + '|' + avatar.preview_video_url,
avatarUi.id + );
'|' + img.onclick = () => {
avatar.name + const avatarImages = document.querySelectorAll('.avatarImg');
'|' + avatarImages.forEach((image) => {
avatarUi.default_voice.free.voice_id + image.style.border = 'none';
'|' + });
avatarUi.video_url.grey, img.style.border = 'var(--border)';
); const avatarData = img.getAttribute('avatarData');
img.onclick = () => { const avatarDataArr = avatarData.split('|');
const avatarImages = document.querySelectorAll('.avatarImg'); VideoAI.avatarId = avatarDataArr[0];
avatarImages.forEach((image) => { VideoAI.avatarName = avatarDataArr[1];
image.style.border = 'none';
});
img.style.border = 'var(--border)';
const avatarData = img.getAttribute('avatarData');
const avatarDataArr = avatarData.split('|');
VideoAI.avatarId = avatarDataArr[0];
VideoAI.avatarName = avatarDataArr[1];
//VideoAI.avatarVoice = avatarDataArr[2] ? avatarDataArr[2] : ''; use the default one
avatarVideoAIPreview.setAttribute('src', avatarUi.video_url.grey); avatarVideoAIPreview.setAttribute('src', avatarDataArr[2]);
avatarVideoAIPreview.play(); avatarVideoAIPreview.play();
console.log('Avatar image click event', { console.log('Avatar image click event', {
avatar, avatar,
avatarUi, avatarDataArr,
avatarDataArr, });
}); };
}; div.append(img);
div.append(img); div.append(hr);
div.append(hr); div.append(label);
div.append(label); avatarVideoAIcontainer.append(div);
avatarVideoAIcontainer.append(div);
// Show the first available free avatar // Show the first available free avatar
if (showFreeAvatars && avatarUi.pose_name === 'Kristin in Black Suit') { if (showFreeAvatars && avatar.avatar_name === 'Kristin in Black Suit') {
avatarVideoAIPreview.setAttribute('src', avatarUi.video_url.grey); avatarVideoAIPreview.setAttribute('src', avatar.preview_video_url);
avatarVideoAIPreview.playsInline = true; avatarVideoAIPreview.playsInline = true;
avatarVideoAIPreview.autoplay = true; avatarVideoAIPreview.autoplay = true;
avatarVideoAIPreview.controls = true; avatarVideoAIPreview.controls = true;
avatarVideoAIPreview.volume = 0.5; avatarVideoAIPreview.volume = 0.5;
}
} }
}); }
}); });
}) })
.catch((err) => { .catch((err) => {
@@ -8413,40 +8403,52 @@ class RoomClient {
getVoiceList() { getVoiceList() {
this.socket this.socket
.request('getVoiceList') .request('getVoiceList')
.then(function (completion) { .then((completion) => {
//console.log('VOICES LISTS', completion.response.voices);
// Ensure the response has the list of voices
const voiceList = completion?.response?.voices ?? [];
if (!voiceList.length) {
console.warn('No voices available in the response');
return;
}
const selectElement = document.getElementById('avatarVoiceIDs'); const selectElement = document.getElementById('avatarVoiceIDs');
selectElement.innerHTML = '<option value="">Select Avatar Voice</option>'; // Reset options with default selectElement.innerHTML = '<option value="">Select Avatar Voice</option>'; // Reset options with default
// Sort the list alphabetically by language // Sort the list alphabetically by language
const sortedList = completion.response.list.sort((a, b) => a.language.localeCompare(b.language)); const sortedList = voiceList.sort((a, b) => (a.language ?? '').localeCompare(b.language ?? ''));
sortedList.forEach((flag) => { // Populate the select element with options
// console.log('flag', flag); sortedList.forEach((voice) => {
const { is_paid, voice_id, language, display_name, gender } = flag; const { is_paid, voice_id, language, display_name, gender } = voice;
if (showFreeAvatars ? is_paid == false : true) { if (showFreeAvatars ? !is_paid : true) {
const option = document.createElement('option'); const option = document.createElement('option');
option.value = voice_id; option.value = voice_id;
option.text = `${language}, ${display_name} (${gender})`; // You can customize the display text option.textContent = `${language ?? 'Unknown'}, ${display_name ?? 'Unnamed'} (${gender ?? 'N/A'})`;
selectElement.appendChild(option); selectElement.appendChild(option);
} }
}); });
// Event listener for changes on select element // Event listener for changes on the select element
selectElement.addEventListener('change', (event) => { selectElement.addEventListener('change', (event) => {
const selectedVoiceID = event.target.value; const selectedVoiceID = event.target.value;
const selectedPreviewURL = completion.response.list.find( const selectedVoice = voiceList.find((voice) => voice.voice_id === selectedVoiceID);
(flag) => flag.voice_id === selectedVoiceID,
)?.preview?.movio; VideoAI.avatarVoice = selectedVoiceID || null;
VideoAI.avatarVoice = selectedVoiceID ? selectedVoiceID : null;
if (selectedPreviewURL) { const previewAudioURL = selectedVoice?.preview_audio;
if (previewAudioURL) {
const avatarPreviewAudio = document.getElementById('avatarPreviewAudio'); const avatarPreviewAudio = document.getElementById('avatarPreviewAudio');
avatarPreviewAudio.src = selectedPreviewURL; avatarPreviewAudio.src = previewAudioURL;
avatarPreviewAudio.play(); avatarPreviewAudio.play().catch((err) => {
console.error('Error playing preview audio:', err);
});
} }
}); });
}) })
.catch((err) => { .catch((err) => {
console.error('Video AI getVoiceList error:', err); console.error('Video AI getVoiceList error', err);
}); });
} }