Vtuber-GPT-TTS

用于模拟主播直播用的文字转语音脚本

目前為 2023-10-23 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Vtuber-GPT-TTS
// @namespace    GPT-TTS
// @version      0.1
// @description  用于模拟主播直播用的文字转语音脚本
// @author       ollwhl
// @match        https://flowgpt.com/chat
// @match        https://chat.openai.com/*
// @grant        GM_xmlhttpRequest
// @require      https://cdn.bootcdn.net/ajax/libs/jquery/3.6.1/jquery.min.js
// @run-at       document-end
// @license      MIT
// ==/UserScript==
(function() {

    'use strict';
    var useGradioAPI=true;
    //是否使用Grdio的Api,如果不使用填写false
    //use gradioApi or not
    //GradioAPiを使うか、使わないならfalseをいれてください
    var localURL="http://127.0.0.1:5000";
    //填写你的本地TTS服务器地址
    //Enter your local TTS server address.
    //ローカルのTTSサーバーのアドレスを入力してください
    var gradioURL="https://xzjosh-carol-bert-vits2.hf.space/--replicas/x6tfx/";
    var gradioSpeaker="Carol"
    //填写Gradio服务器的地址,必须是在外部服务器部署的,如果是本地部署的Gradio没法读取文件,如果使用请先到Gradio的GUI上查看via API地址
    //Enter the address of the Gradio server. It must be deployed on an external server, as locally deployed Gradio cannot access files.Before using, please first check the 'via API' address on the Gradio GUI.
    //Gradioサーバーのアドレスを入力してください。外部サーバーにデプロイされている必要があり、ローカルにデプロイされたGradioはファイルにアクセスできません。使用する前に、GradioのGUIで'via API'のアドレスを確認してください。
    var inputQueue = [
        "台本:向直播间的观众轻轻地微笑,展现出自己的特有魅力。",
        "台本:向直播间的观众问好,用温柔的语气回应。",
        "台本:询问今天观众们吃的好不好,是否有特殊的推荐。",
        "台本:向观众分享自己今天吃的午饭,描述食物的味道和感觉。",
        "台本:提及自己最近的活动和经历,与观众分享生活中的趣事。",
        "台本:展示一个近期自己特别喜欢的物品或者玩具,向观众展示细节。",
        "台本:询问观众最近有什么新的推荐歌曲,表示自己想要增加歌单。",
        "台本:唱一小段自己喜欢的歌曲,或者是观众点的歌。",
        "台本:邀请观众投票,决定今天的直播主题或活动。",
        "台本:向观众展示一个小小的才艺或技能,如简单的手工或魔术。",
        "台本:与观众分享一个小技巧或者生活hack。",
        "台本:询问观众关于他们的最近旅行或者出行经历。",
        "台本:分享自己最想去的旅行目的地,和观众交流心得。",
        "台本:进行一个小小的Q&A环节,回答观众提出的问题。",
        "台本:读取直播聊天,与观众互动,感谢他们的支持和陪伴。",
        "台本:为观众推荐一个电影或者电视剧,分享观后感。",
        "台本:播放一个短视频或者音乐,与观众分享感受。",
        "台本:进行一个小游戏环节,邀请观众参与,互动氛围更浓。",
        "台本:为一位幸运观众送出一个小礼物或者特别的祝福。",
        "台本:向观众告知直播即将结束,感谢他们的陪伴。",
        "台本:祝愿观众们都有一个美好的一天,微笑并挥手道别。"
    ];
    var useLive2D = false;
    var toggleButton = document.createElement("button");
    if (!useGradioAPI) {
        toggleButton.textContent = "using LocalAPI";
        toggleButton.title = "Click to switch to Gradio API";
    } else {
        toggleButton.textContent = "using GradioAPI";
        toggleButton.title = "Click to switch to Local API";
    }
    toggleButton.style.position = 'fixed';
    toggleButton.style.top = '200px';
    toggleButton.style.right = '10px';
    toggleButton.style.zIndex = '9999';
    toggleButton.addEventListener("click", function () {
        useGradioAPI = !useGradioAPI;
        if (!useGradioAPI) {
            toggleButton.textContent = "using LocalAPI";
            toggleButton.title = "Click to switch to Gradio API";
        } else {
            toggleButton.textContent = "using GradioAPI";
            toggleButton.title = "Click to switch to Local API";
        }
    });
    document.body.appendChild(toggleButton);

    var massageClass;
    var baseDomain = window.location.hostname;
    if (baseDomain === "chat.openai.com") {
        massageClass=".prose";
    } else if (baseDomain === "flowgpt.com") {
        massageClass="div.flowgpt-markdown.prose-invert.flex-1.whitespace-pre-wrap.markdown-body.overflow-auto";
    } else {
        console.log("This is some other domain!");
    }

    document.addEventListener("beforescriptexecute", function(event) {
        var oldCSP = document.querySelector("meta[http-equiv='Content-Security-Policy']");
        if (oldCSP) {
            oldCSP.remove();
        }

        // Add the new CSP
        var newCSP = document.createElement("meta");
        newCSP.httpEquiv = "Content-Security-Policy";
        newCSP.content = "default-src 'self' https://xzjosh-carol-bert-vits2.hf.space; media-src 'self' https://xzjosh-carol-bert-vits2.hf.space;";
        document.head.appendChild(newCSP);
    });


    let startButton = document.createElement('button');
    startButton.innerText = 'StartTTS';
    startButton.style.position = 'fixed';
    startButton.style.top = '100px';
    startButton.style.right = '10px';
    startButton.style.zIndex = '9999';
    startButton.addEventListener('click', startScript);
    document.body.appendChild(startButton);

    let stopButton = document.createElement('button');
    stopButton.innerText = 'StopTTS';
    stopButton.style.position = 'fixed';
    stopButton.style.top = '150px';
    stopButton.style.right = '10px';
    stopButton.style.zIndex = '9999';
    stopButton.addEventListener('click', stopListening(massageClass));
    document.body.appendChild(stopButton);

    console.log("TTS script activate");
    var isListeningDomChange =false;

    let inputButton = document.createElement('button');
    inputButton.innerText = 'start act';
    inputButton.style.position = 'fixed';
    inputButton.style.top = '250px';
    inputButton.style.right = '10px';
    inputButton.style.zIndex = '9999';
    function startScript() {
        if(!isListeningDomChange){
            alert('TTS has started!');
             if (baseDomain === "flowgpt.com"){
            listenDOMChange('.chakra-text.css-1pw8fnh');
             }
            isListeningDomChange =true;
            startButton.innerText = 'TTS STARTED';
        }else{
            console.log("Speaker changed");
        }
        $(massageClass).addClass('processed');

        listenForNewElements('body',massageClass,checkText)
        inputButton.addEventListener('click', function() {
            startAct();
        });
        document.body.appendChild(inputButton);
        //listenKey(massageClass,checkText)
    }
    //setTimeout(function() {
    //    console.log("script activate 1");
    //    $('div.flowgpt-markdown.prose-invert.flex-1.whitespace-pre-wrap.markdown-body.overflow-auto').addClass('processed');
    //    listenKey('div.flowgpt-markdown.prose-invert.flex-1.whitespace-pre-wrap.markdown-body.overflow-auto',checkText);
    //}, 5000);
    const skip=0;
    var skipCount = skip;
    const requestQueue = [];
    var isRequestPending = false;
    var textChecked = false;
    var inputIndex = 0;
    var inputElement = '#prompt-textarea';
    console.log(inputElement);
    function inputText(inputElement){
       var element = $(inputElement);
        console.log("input element:",element);
        if (inputIndex < inputQueue.length) {
            var currentText = inputQueue[inputIndex];
            inputIndex++;
            element.focus(); // 确保元素获取焦点
            let inputLabel = $(inputElement); //这里获取需要自动录入的input内容
            let lastValue = inputLabel[0].value;
            inputLabel[0].value = currentText;
            let event = new Event("input", { bubbles: true });
            //  React15
            event.simulated = true;
            //  React16 内部定义了descriptor拦截value,此处重置状态
            let tracker = inputLabel[0]._valueTracker;
            if (tracker) {
                tracker.setValue(lastValue);
            }
            inputLabel[0].dispatchEvent(event);
            var buttonToClick = $("button[data-testid='send-button']");
            buttonToClick.click();
            isTextFinish=false;
        } else {
            alert("已经输入完所有文本");
            console.log('停止');
            inputButton.innertext='开始';
            clearInterval(intervalId);
            inputIndex=0;
        }
    }
    var isRunningAct=false;
    var intervalId;
    var isTextFinish = true;
    function startAct () {
        isRunningAct=!isRunningAct;
        if (isRunningAct) {
            console.log('开始');
            inputButton.innertext='停止';
            intervalId = setInterval(function () {
                if(isTextFinish){
                    inputText(inputElement);
                }
            }, 1000);
        } else {
            console.log('停止');
            inputButton.innertext='开始';
            clearInterval(intervalId);
        }
    }
    function checkText(element) {
        textChecked = true;
        if (!element.hasClass('processed')) {
            console.log(skipCount);
            if (skipCount > 0) {
                skipCount--;
            } else {
                skipCount = skip;
                var text = element.text();
                console.log(text);
                requestQueue.push(element)
                console.log("push request:",requestQueue.length);
                sendNextText();
                element.addClass('processed');
            }
        }
    }
    function removeTextWithinBrackets(str) {
        return str.replace(/(\(.*?\))|(.*?)|{.*?\}/g, '');
    }
    function strToJson(str){
        str=str.trim();
        if(str.includes("jsonCopy code")||str.includes("data")){
            const jsonIndex = str.indexOf("data");
            var tmpStr = str.substring(jsonIndex-1);
            console.log(tmpStr)
            tmpStr=tmpStr.trim();
            tmpStr = `{${tmpStr}}`;
            try {
                var json=JSON.parse(tmpStr);
                console.log("json化成功:",json);
                return json;
            } catch (error) {
                console.log("json化失败:",tmpStr);
                return removeTextWithinBrackets(str);
            }
        }else{
            console.log("不是json类型");
            return removeTextWithinBrackets(str);
        }
    }
    var elementCompair;
    var audioQueue = [];
    var isPlaying = false;
    function sendText(element) {
        if (isRequestPending) {
            console.log("等待上一个响应完成...");
            return;
        }
        isRequestPending = true;
        var json = strToJson(element.text());
        var text;
        if(useLive2D){
            useGradioAPI=false;
            text=json;
        }else{
            try{
                text=json.data[0]
            }catch{
                text=json;
            }
        }
        if(text===""||text===" "){
            isRequestPending = false;
            sendText(element);
        }
        if(useGradioAPI){
            console.log("sending request to gradioServer:",text );
            sendTextToGrodio(element);
        }else{
            console.log("sending request to localServer:",text );
            GM_xmlhttpRequest({
                method: "GET",
                url: "http://127.0.0.1:5000?text=" + encodeURIComponent(text),
                responseType: 'arraybuffer',
                onload: function(response) {
                    if (response.status >= 200 && response.status < 400) {
                        console.log("成功:", response);
                        isRequestPending = false;
                        var clonedBuffer = response.response.slice(0);
                        if(elementCompair!=element){
                            var playButton = $('<button>').text('播放').click(function() {
                                //playAudioWithBlob(savedAudioDataMap[text]);
                                //var audioBuffer = $(this).data(clonedBuffer);
                                elementCompair=element;
                                var jThis = element;
                                console.log("index :",element.text(),"data: ",element)
                                sendText(element);
                            });
                            element.after(playButton);
                        }
                        audioQueue.push(clonedBuffer);
                        playNextAudio();
                        //playAudio(clonedBuffer);
                        //playAudioWithBlob(savedAudioDataMap[text]);
                    } else {
                        console.log("出错:", response.statusText);
                        var textElement = $("<p>请求出错,请按F12查看控制台信息</p>");
                        element.after(textElement);
                    }
                },
                onerror: function(error) {
                    console.log("请求失败:", error);
                    var textElement = $("<p>请求失败,请按F12查看控制台信息</p>");
                    element.after(textElement);
                }
            });
        }
        sendNextText()
    }
    function sendNextText(){
        var interval;
        interval = setInterval(function () {
            if(requestQueue.length===0){
                console.log("STOP SEND")
                clearInterval(interval)
            }
            if(!isRequestPending){
                console.log("shift request:",requestQueue.length);
                sendText(requestQueue.shift());
            }else{
                console.log("wait last request respons");
            }
        }, 100); // 1秒延迟,根据需要调整
    }
    async function sendTextToGrodio(element){
        const urlObj = new URL(gradioURL);
        const baseDomain = urlObj.origin;
        var text = removeTextWithinBrackets(element.text());
        GM_xmlhttpRequest({
            method: "POST",
            url: baseDomain+"/run/predict",
            headers: {
                "Content-Type": "application/json",
            },
            data: JSON.stringify({
                "fn_index": 0,
                "data": [
                    text,
                    gradioSpeaker,
                    0.5,
                    0.6,
                    0.6,
                    1.2
                ]
            }),
            onload: function(response) {
                console.log("成功:", response);
                var path;
                var filePath;
                if (response && response.responseText) {
                    try {
                        const parsedResponse = JSON.parse(response.responseText);
                        if (Array.isArray(parsedResponse.data) && parsedResponse.data.length > 1) {
                            path = parsedResponse.data[1].name;
                            filePath=gradioURL+'file='+path;
                            console.log("filePath:", filePath);
                        } else {
                            console.error("Unexpected parsed response:", parsedResponse);
                        }
                    } catch (e) {
                        console.error("Failed to parse JSON:", e);
                    }
                } else {
                    console.error("Invalid or empty response:", response);
                }
                const id = path.match(/gradio\/([a-f0-9]+)\/audio\.wav/);
                console.log("sending request to :", filePath);
                GM_xmlhttpRequest({
                    method: "GET",
                    url: filePath,
                    responseType: 'arraybuffer',
                    onload: function(response) {
                        if (response.status >= 200 && response.status < 400) {
                            console.log("成功:", response);
                            isRequestPending = false;
                            var clonedBuffer = response.response.slice(0);
                            if(elementCompair!=element){
                                var playButton = $('<button>').text('播放').click(function() {
                                    GM_xmlhttpRequest({
                                        method: "GET",
                                        url: filePath,
                                        responseType: 'arraybuffer',
                                        onload: function(response) {
                                            if (response.status >= 200 && response.status < 400) {
                                                isSending=false;
                                                console.log("成功:", response);
                                                var clonedBuffer = response.response.slice(0);
                                                audioQueue.push(clonedBuffer);
                                                playNextAudio();
                                                //playAudioWithBlob(savedAudioDataMap[text]);
                                            } else {
                                                console.log("出错:", response.statusText);
                                                var textElement = $("<p>请求出错,请按F12查看控制台信息</p>");
                                                element.after(textElement);
                                            }
                                        },
                                        onerror: function(error) {
                                            console.log("请求失败:", error);
                                            var textElement = $("<p>请求失败,请按F12查看控制台信息</p>");
                                            element.after(textElement);
                                        }
                                    });
                                });
                                element.after(playButton);
                            }
                            audioQueue.push(clonedBuffer);
                            playNextAudio();
                            //playAudioWithBlob(savedAudioDataMap[text]);
                        } else {
                            console.log("出错:", response.statusText);
                        }
                    },
                    onerror: function(error) {
                        console.log("请求失败:", error);
                        var textElement = $("<p>请求出错,请按F12查看控制台信息</p>");
                        targetElement.after(textElement);
                    }
                });
            },
            onerror: function(response) {
                console.log("Error: ", response);
            }
        })};
    function addPlayButton(element,text){
        var playButton = $('<button>').text('播放').click(function() {
            playAudio(savedAudioDataMap[text]);
        });
        element.after(playButton);
    }
    function playAudioWithBlob(audioData, mimeType = 'audio/wav') {
        const blob = new Blob([audioData], { type: mimeType });
        const audio = new Audio(URL.createObjectURL(blob));
        audio.play();
    }
    async function playAudio(audioData) {
        const audioContext = new (window.AudioContext || window.webkitAudioContext)();

        try {
            const buffer = await audioContext.decodeAudioData(audioData);
            const audioSource = audioContext.createBufferSource();
            audioSource.buffer = buffer;
            audioSource.connect(audioContext.destination);
            audioSource.start();
        } catch (error) {
            console.error('Error decoding audio data:', error);
        }
    }
    function playNextAudio() {
        if (audioQueue.length > 0 && !isPlaying) {
            isPlaying = true;
            var audioData = audioQueue.shift();

            var audioContext = new (window.AudioContext || window.webkitAudioContext)();
            audioContext.decodeAudioData(audioData, function (buffer) {
                var audioSource = audioContext.createBufferSource();
                audioSource.buffer = buffer;
                audioSource.connect(audioContext.destination);
                audioSource.start();
                audioSource.onended = function () {
                    isPlaying = false;
                    playNextAudio();
                };
            });
        }
    }
    function listenDOMChange(selectorTxt) {
        var targetNode = $(selectorTxt).get(0);

        var config = { childList: true, subtree: true, characterData: true };

        var callback = function(mutationsList) {
            for(var mutation of mutationsList) {
                if (mutation.type === 'childList' || mutation.type === 'characterData') {
                    //stopListening('div.flowgpt-markdown.prose-invert.flex-1.whitespace-pre-wrap.markdown-body.overflow-auto');
                    startScript();
                }
            }
        };

        var observer = new MutationObserver(callback);
        observer.observe(targetNode, config);
    };
    var isListening = false; // 添加一个标志来表示是否正在监听
    function listenKey(selectorTxt, actionFunction, bWaitOnce, iframeSelector) {
        // 如果正在监听,则直接返回,避免重复启动监听
        if (isListening) {
            return;
        }

        // 设置正在监听的标志
        isListening = true;

        var targetNodes, btargetsFound;

        if (typeof iframeSelector == "undefined")
            targetNodes = $(selectorTxt);
        else
            targetNodes = $(iframeSelector).contents()
                .find(selectorTxt);

        if (targetNodes && targetNodes.length > 0) {
            btargetsFound = true;
            targetNodes.each(function () {
                var jThis = $(this);
                var alreadyFound = jThis.data('alreadyFound') || false;

                if (!alreadyFound) {
                    // 添加识别的类,例如 'listened-element'
                    jThis.addClass('listened-element');

                    //--- Check if the DOM content is changing.
                    var initialContent = jThis.html();
                    var cancelFound = false;
                    var interval = setInterval(function () {
                        var currentContent = jThis.html();
                        if (initialContent !== currentContent) {
                            clearInterval(interval);
                            //--- Call the payload function.
                            cancelFound = actionFunction(jThis);
                            jThis.data('alreadyFound', true);
                            jThis.removeClass('listened-element'); // 移除识别的类
                            if (!cancelFound) {
                                listenKey(selectorTxt, actionFunction, bWaitOnce, iframeSelector);
                            }
                        }
                    }, 2000);
                }
            });
        } else {
            btargetsFound = false;
        }

        var controlObj = listenKey.controlObj || {};
        var controlKey = selectorTxt.replace(/[^\w]/g, "_");
        var timeControl = controlObj[controlKey];

        //--- Now set or clear the timer as appropriate.
        if (btargetsFound && bWaitOnce && timeControl) {
            clearInterval(timeControl);
            delete controlObj[controlKey];
        } else {
            if (!timeControl) {
                timeControl = setInterval(function () {
                    listenKey(selectorTxt, actionFunction, bWaitOnce, iframeSelector);
                }, 300);
                controlObj[controlKey] = timeControl;
            }
        }

        // 清除监听标志,以便在下次监听时可以重新启动
        isListening = false;
        listenKey.controlObj = controlObj;
    }
    function listenForNewElements(parentSelector, childSelector, actionFunction, iframeSelector) {
        var parentNodes;

        if (typeof iframeSelector == "undefined") {
            parentNodes = $(parentSelector);
        } else {
            parentNodes = $(iframeSelector).contents().find(parentSelector);
        }

        parentNodes.each(function() {
            var jThis = $(this);

            // 使用MutationObserver来监听DOM变化
            var observer = new MutationObserver(function(mutations) {
                mutations.forEach(function(mutation) {
                    if (mutation.type === 'childList') {
                        $(mutation.addedNodes).each(function() {
                            var newNode = $(this);
                            if (newNode.is(childSelector)) {
                                waitForElementStability3(newNode, actionFunction);
                            }
                        });
                    }
                });
            });

            var config = { childList: true, subtree: true };
            observer.observe(jThis[0], config);
        });
    }
    function waitForElementStability1(element, actionFunction) {
        var lastContent = element.html();
        var stabilityTimer = null;
        var stabilityDelay = 1000; // 设定稳定性的时间,例如1秒

        var observer = new MutationObserver(function() {
            console.log($('.btn.relative.btn-neutral').text().includes("Stop generating"));
            if (element.html() !== lastContent&&!$('.btn.relative.btn-neutral').text().includes("Stop generating")) {
                lastContent = element.html();
                if (stabilityTimer) {
                    clearTimeout(stabilityTimer); // 如果内容变了,重置定时器
                }
                stabilityTimer = setTimeout(function() {
                    actionFunction(element);
                    observer.disconnect(); // 停止观察,因为元素已经稳定
                }, stabilityDelay);
            }
        });

        observer.observe(element[0], { childList: true, subtree: true });
    }
    function waitForElementStability3(element, actionFunction, checkInterval) {
        checkInterval = checkInterval || 1000;  // 默认每500ms检查一次
        var interval = setInterval(function() {
            console.log(element.length && !$('.btn.relative.btn-neutral').text().includes("Stop generating"),)
            if (element.length && !$('.btn.relative.btn-neutral').text().includes("Stop generating")) {
                isTextFinish=true;
                actionFunction(element);
                clearInterval(interval);
            }
        }, checkInterval);
    }
    function waitForElementStability2(element, actionFunction, waitTime) {
        var timer = null;
        waitTime = waitTime || 500; // 默认等待时间是500ms

        if (!element.length) {
            // 如果元素不存在,不进行后续操作
            return;
        }
        // 创建一个观察器实例并传入回调函数
        var observer = new MutationObserver(function(mutations) {
            // 如果有一个正在等待的定时器,清除它
            if (timer) {
                clearTimeout(timer);
            }

            // 设置新的定时器
            timer = setTimeout(function() {
                if (!$('.btn.relative.btn-neutral').text().includes("Stop generating")) {
                    console.log($('.btn.relative.btn-neutral').text());
                    actionFunction(element);
                    timer = null;
                }
            }, waitTime);
        });

        // 以配置对象参数开始观察目标节点
        observer.observe(element[0], { childList: true, subtree: true });
    }
    function stopListening(elementText){
        startButton.innerText = 'StartTTS'
        var myElement = $(elementText);
        var clonedElement = myElement.clone(true);
        myElement.replaceWith(clonedElement);
    }
    // Your code here...
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址