(已解决:用4o写了个油猴插件) 请问有没有适合调用OpenAI TTS的应用或插件?

POST一般格式是:https:/xxx.xxx/v1/audio/speech
新增的内容是:

  1. 设置界面:


  2. 文本框按钮,可自行填入文本进行转换:


很开心有机会捣鼓东西,这应该是来到linux.do我才会做的事情。 :kissing_heart:

// ==UserScript==
// @name         OpenAI TTS API Converter
// @namespace    http://tampermonkey.net/
// @version      2.1
// @description  调用第三方站点api将选中的文本或输入的文本转换为语音并下载
// @author       finch
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @license      MIT
// ==/UserScript==
 
(function() {
    'use strict';
 
    // 加载 Font Awesome CSS
    const fontAwesomeLink = document.createElement('link');
    fontAwesomeLink.rel = 'stylesheet';
    fontAwesomeLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css';
    document.head.appendChild(fontAwesomeLink);
 
    // 获取用户配置
    function getUserConfig() {
        return {
            bearerToken: localStorage.getItem('tts_bearerToken') || '',
            postUrl: localStorage.getItem('tts_postUrl') || '',
            model: localStorage.getItem('tts_model') || '',
            voice: localStorage.getItem('tts_voice') || ''
        };
    }
 
    // 设置用户配置
    function setUserConfig(config) {
        localStorage.setItem('tts_bearerToken', config.bearerToken);
        localStorage.setItem('tts_postUrl', config.postUrl);
        localStorage.setItem('tts_model', config.model);
        localStorage.setItem('tts_voice', config.voice);
    }
 
    // 创建设置对话框
    function createSettingsDialog() {
        const dialog = document.createElement('div');
        dialog.style.position = 'fixed';
        dialog.style.top = '50%';
        dialog.style.left = '50%';
        dialog.style.transform = 'translate(-50%, -50%)';
        dialog.style.backgroundColor = 'white';
        dialog.style.padding = '20px';
        dialog.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.3)';
        dialog.style.zIndex = 10000;
        dialog.style.display = 'none';
        dialog.style.borderRadius = '8px';
        dialog.style.maxWidth = '400px';
        dialog.style.width = '100%';
 
        const config = getUserConfig();
 
        const title = document.createElement('h2');
        title.textContent = '设置 TTS API 配置';
        title.style.marginTop = '0';
        title.style.marginBottom = '20px';
        title.style.textAlign = 'center';
        dialog.appendChild(title);
 
        const form = document.createElement('form');
        form.style.display = 'flex';
        form.style.flexDirection = 'column';
        form.style.gap = '10px';
 
        const createInputField = (labelText, inputValue) => {
            const label = document.createElement('label');
            label.textContent = labelText;
            label.style.fontWeight = 'bold';
            const input = document.createElement('input');
            input.type = 'text';
            input.value = inputValue;
            input.style.width = '100%';
            input.style.padding = '8px';
            input.style.border = '1px solid #ccc';
            input.style.borderRadius = '4px';
            label.appendChild(input);
            return { label, input };
        };
 
        const bearerTokenField = createInputField('Bearer 令牌:', config.bearerToken);
        const postUrlField = createInputField('POST URL:', config.postUrl);
        const modelField = createInputField('模型名称:', config.model);
        const voiceField = createInputField('声音名称:', config.voice);
 
        form.appendChild(bearerTokenField.label);
        form.appendChild(postUrlField.label);
        form.appendChild(modelField.label);
        form.appendChild(voiceField.label);
 
        const buttonContainer = document.createElement('div');
        buttonContainer.style.display = 'flex';
        buttonContainer.style.justifyContent = 'space-between';
        buttonContainer.style.marginTop = '20px';
 
        const saveButton = document.createElement('button');
        saveButton.textContent = '保存';
        saveButton.style.backgroundColor = '#4CAF50';
        saveButton.style.color = 'white';
        saveButton.style.border = 'none';
        saveButton.style.padding = '10px 20px';
        saveButton.style.borderRadius = '4px';
        saveButton.style.cursor = 'pointer';
        saveButton.addEventListener('click', (e) => {
            e.preventDefault();
            setUserConfig({
                bearerToken: bearerTokenField.input.value,
                postUrl: postUrlField.input.value,
                model: modelField.input.value,
                voice: voiceField.input.value
            });
            dialog.style.display = 'none';
        });
 
        const cancelButton = document.createElement('button');
        cancelButton.textContent = '取消';
        cancelButton.style.backgroundColor = '#f44336';
        cancelButton.style.color = 'white';
        cancelButton.style.border = 'none';
        cancelButton.style.padding = '10px 20px';
        cancelButton.style.borderRadius = '4px';
        cancelButton.style.cursor = 'pointer';
        cancelButton.addEventListener('click', (e) => {
            e.preventDefault();
            dialog.style.display = 'none';
        });
 
        buttonContainer.appendChild(saveButton);
        buttonContainer.appendChild(cancelButton);
        form.appendChild(buttonContainer);
 
        // 添加测试按钮
        const testButton = document.createElement('button');
        testButton.textContent = '点此测试服务';
        testButton.style.marginTop = '10px';
        testButton.style.backgroundColor = '#2196F3';
        testButton.style.color = 'white';
        testButton.style.border = 'none';
        testButton.style.padding = '10px 20px';
        testButton.style.borderRadius = '4px';
        testButton.style.cursor = 'pointer';
        testButton.addEventListener('click', (e) => {
            e.preventDefault();
            testTTSAPI();
        });
 
        form.appendChild(testButton);
 
        dialog.appendChild(form);
        document.body.appendChild(dialog);
        return dialog;
    }
 
    const settingsDialog = createSettingsDialog();
 
    // 配置菜单
    GM_registerMenuCommand('设置 TTS API 配置', () => {
        settingsDialog.style.display = 'block';
    });
 
    // 测试 TTS API
    function testTTSAPI() {
        const config = getUserConfig();
        const data = {
            model: config.model,
            input: 'Hello, world',
            voice: config.voice
        };
 
        GM_xmlhttpRequest({
            method: 'POST',
            url: config.postUrl,
            headers: {
                'Authorization': `Bearer ${config.bearerToken}`,
                'Content-Type': 'application/json'
            },
            data: JSON.stringify(data),
            responseType: 'blob',
            onload: function(response) {
                if (response.status === 200) {
                    alert('TTS API 连通性测试成功!');
                } else {
                    alert('TTS API 连通性测试失败,请检查配置。');
                }
            }
        });
    }
 
    // 将文本转换为语音并下载
    function convertTextToSpeech(text, download = false) {
        const config = getUserConfig();
        const data = {
            model: config.model,
            input: text,
            voice: config.voice
        };
 
        GM_xmlhttpRequest({
            method: 'POST',
            url: config.postUrl,
            headers: {
                'Authorization': `Bearer ${config.bearerToken}`,
                'Content-Type': 'application/json'
            },
            data: JSON.stringify(data),
            responseType: 'blob',
            onload: function(response) {
                if (response.status === 200) {
                    const url = URL.createObjectURL(response.response);
                    if (download) {
                        const downloadLink = document.createElement('a');
                        downloadLink.href = url;
                        downloadLink.download = 'tts_audio.mp3'; // 默认文件名
                        downloadLink.click();
                    } else {
                        const audio = new Audio(url);
                        audio.play();
                        // 显示下载按钮
                        downloadButton.style.display = 'block';
                        downloadButton.onclick = () => {
                            const downloadLink = document.createElement('a');
                            downloadLink.href = url;
                            downloadLink.download = 'tts_audio.mp3';
                            downloadLink.click();
                        };
                    }
                } else {
                    console.error(`请求失败,状态码:${response.status}`);
                }
            }
        });
    }
 
    // 创建按钮容器
    const buttonContainer = document.createElement('div');
    buttonContainer.style.position = 'absolute';
    buttonContainer.style.zIndex = 1000;
    buttonContainer.style.display = 'flex';
    buttonContainer.style.gap = '8px'; // 按钮间距
    buttonContainer.style.display = 'none'; // 初始隐藏
    document.body.appendChild(buttonContainer);
 
    // 按钮样式
    const buttonStyle = {
        width: '40px',
        height: '40px',
        borderRadius: '50%',
        border: 'none',
        cursor: 'pointer',
        fontSize: '1.5em',
        color: 'white'
    };
 
    // 创建转换语音按钮
    const playButton = document.createElement('button');
    playButton.innerHTML = '<i class="fas fa-play"></i>'; // 使用 Font Awesome 播放图标
    Object.assign(playButton.style, buttonStyle, { backgroundColor: '#4CAF50' });
    playButton.addEventListener('click', () => {
        const selection = window.getSelection();
        const text = selection.toString();
        if (text) {
            convertTextToSpeech(text);
        } else {
            alert('没有选中文本');
        }
    });
    buttonContainer.appendChild(playButton);
 
    // 创建下载按钮
    const downloadButton = document.createElement('button');
    downloadButton.innerHTML = '<i class="fas fa-download"></i>'; // 使用 Font Awesome 下载图标
    Object.assign(downloadButton.style, buttonStyle, { backgroundColor: '#2196F3' });
    downloadButton.style.display = 'none'; // 初始隐藏
    buttonContainer.appendChild(downloadButton);
 
    // 创建输入文本按钮
    const inputButton = document.createElement('button');
    inputButton.innerHTML = '<i class="fas fa-file-alt"></i>'; // 使用 Font Awesome 纸张图标
    Object.assign(inputButton.style, buttonStyle, { backgroundColor: '#FF9800' });
    inputButton.addEventListener('click', () => {
        inputDialog.style.display = 'block';
    });
    buttonContainer.appendChild(inputButton);
 
    // 创建文本输入框页面
    const inputDialog = document.createElement('div');
    inputDialog.style.position = 'fixed';
    inputDialog.style.top = '50%';
    inputDialog.style.left = '50%';
    inputDialog.style.transform = 'translate(-50%, -50%)';
    inputDialog.style.backgroundColor = 'white';
    inputDialog.style.padding = '20px';
    inputDialog.style.boxShadow = '0 4px 15px rgba(0, 0, 0, 0.3)';
    inputDialog.style.zIndex = 10000;
    inputDialog.style.display = 'none';
    inputDialog.style.borderRadius = '8px';
    inputDialog.style.maxWidth = '400px';
    inputDialog.style.width = '100%';
 
    const inputTitle = document.createElement('h2');
    inputTitle.textContent = '输入文本进行转换';
    inputTitle.style.marginTop = '0';
    inputTitle.style.marginBottom = '20px';
    inputTitle.style.textAlign = 'center';
    inputDialog.appendChild(inputTitle);
 
    const inputText = document.createElement('input');
    inputText.type = 'text';
    inputText.placeholder = '输入文本';
    inputText.style.padding = '10px';
    inputText.style.border = '1px solid #ccc';
    inputText.style.borderRadius = '4px';
    inputText.style.width = '100%';
    inputText.style.marginBottom = '20px';
    inputDialog.appendChild(inputText);
 
    const inputButtonContainer = document.createElement('div');
    inputButtonContainer.style.display = 'flex';
    inputButtonContainer.style.justifyContent = 'space-between';
 
    const playInputButton = document.createElement('button');
    playInputButton.textContent = '播放';
    playInputButton.style.backgroundColor = '#4CAF50';
    playInputButton.style.color = 'white';
    playInputButton.style.border = 'none';
    playInputButton.style.padding = '10px 20px';
    playInputButton.style.borderRadius = '4px';
    playInputButton.style.cursor = 'pointer';
    playInputButton.addEventListener('click', () => {
        const text = inputText.value.trim();
        if (text) {
            convertTextToSpeech(text);
        } else {
            alert('请输入文本');
        }
    });
 
    const inputDownloadButton = document.createElement('button');
    inputDownloadButton.textContent = '下载';
    inputDownloadButton.style.backgroundColor = '#4CAF50';
    inputDownloadButton.style.color = 'white';
    inputDownloadButton.style.border = 'none';
    inputDownloadButton.style.padding = '10px 20px';
    inputDownloadButton.style.borderRadius = '4px';
    inputDownloadButton.style.cursor = 'pointer';
    inputDownloadButton.addEventListener('click', () => {
        const text = inputText.value.trim();
        if (text) {
            convertTextToSpeech(text, true);
        } else {
            alert('请输入文本');
        }
    });
 
    const cancelInputButton = document.createElement('button');
    cancelInputButton.textContent = '取消';
    cancelInputButton.style.backgroundColor = '#f44336';
    cancelInputButton.style.color = 'white';
    cancelInputButton.style.border = 'none';
    cancelInputButton.style.padding = '10px 20px';
    cancelInputButton.style.borderRadius = '4px';
    cancelInputButton.style.cursor = 'pointer';
    cancelInputButton.addEventListener('click', () => {
        inputDialog.style.display = 'none';
    });
 
    inputButtonContainer.appendChild(playInputButton);
    inputButtonContainer.appendChild(inputDownloadButton);
    inputButtonContainer.appendChild(cancelInputButton);
    inputDialog.appendChild(inputButtonContainer);
    document.body.appendChild(inputDialog);
 
    // 监听文本选择事件
    document.addEventListener('selectionchange', () => {
        const selection = window.getSelection();
        if (selection.rangeCount > 0) {
            const range = selection.getRangeAt(0);
            const rect = range.getBoundingClientRect();
            if (rect.width > 0 && rect.height > 0) {
                buttonContainer.style.top = `${rect.bottom + window.scrollY}px`;
                buttonContainer.style.left = `${rect.right + window.scrollX}px`;
                buttonContainer.style.display = 'flex';
            } else {
                buttonContainer.style.display = 'none';
                downloadButton.style.display = 'none'; // 隐藏下载按钮
            }
        } else {
            buttonContainer.style.display = 'none';
            downloadButton.style.display = 'none'; // 隐藏下载按钮
        }
    });
})();

-------------------- 二级了来编辑一下 :)
我都忘了GPT可以帮我造轮子 :sweat_smile: 本来这也不难(对它而言),它python写好后我转念一想,油猴插件岂不是更方便所以就…

实现了两个我需要的功能:

  1. 选中句子右侧出现TTS按钮,点击后会自动播放
  2. 同时左侧出现下载按钮,点击下载音频

    我想大声的说一句,我爱你 GPT (4o)
// ==UserScript==
// @name         TTS API Converter
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  将选中的文本转换为语音并下载
// @author       finch
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// ==/UserScript==

(function() {
    'use strict';

    // 加载 Font Awesome CSS
    const fontAwesomeLink = document.createElement('link');
    fontAwesomeLink.rel = 'stylesheet';
    fontAwesomeLink.href = 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0-beta3/css/all.min.css';
    document.head.appendChild(fontAwesomeLink);

    // 替换为你的实际 Bearer 令牌
    const bearerToken = 'sk-xxxx';

    function convertTextToSpeech(text) {
        const data = {
            model: 'tts-1', // 使用你实际需要的模型
            input: text, // 使用选中的文本
            voice: 'alloy' // 替换为你想使用的声音
        };

        GM_xmlhttpRequest({
            method: 'POST',
            url: 'https://xxx.xxx/v1/audio/speech', // 站点api
            headers: {
                'Authorization': `Bearer ${bearerToken}`,
                'Content-Type': 'application/json'
            },
            data: JSON.stringify(data),
            responseType: 'blob',
            onload: function(response) {
                if (response.status === 200) {
                    const url = URL.createObjectURL(response.response);
                    const audio = new Audio(url);
                    audio.play();

                    // 创建下载按钮
                    const downloadButton = document.createElement('a');
                    downloadButton.href = url;
                    downloadButton.download = 'tts_audio.mp3'; // 默认文件名
                    downloadButton.innerHTML = '<i class="fas fa-download"></i>'; // 使用 Font Awesome 图标
                    downloadButton.style.position = 'absolute';
                    downloadButton.style.zIndex = 1000;
                    downloadButton.style.backgroundColor = '#4CAF50';
                    downloadButton.style.color = 'white';
                    downloadButton.style.borderRadius = '50%';
                    downloadButton.style.padding = '0.5em';
                    downloadButton.style.cursor = 'pointer';
                    downloadButton.style.textDecoration = 'none';

                    // 移除之前的下载按钮
                    const existingDownloadButton = document.querySelector('a[download="tts_audio.mp3"]');
                    if (existingDownloadButton) {
                        existingDownloadButton.remove();
                    }

                    // 将下载按钮放置在转换语音按钮的左侧
                    downloadButton.style.top = button.style.top;
                    downloadButton.style.left = `${parseInt(button.style.left, 10) - 40}px`;

                    document.body.appendChild(downloadButton);
                } else {
                    console.error(`请求失败,状态码:${response.status}`);
                }
            }
        });
    }

    // 创建转换语音按钮
    const button = document.createElement('button');
    button.textContent = 'TTS';
    button.style.position = 'absolute';
    button.style.zIndex = 1000;
    button.style.backgroundColor = '#4CAF50';
    button.style.color = 'white';
    button.style.border = 'none';
    button.style.padding = '10px';
    button.style.cursor = 'pointer';
    button.style.display = 'none'; // 初始隐藏
    button.addEventListener('click', () => {
        const selection = window.getSelection();
        const text = selection.toString();
        if (text) {
            convertTextToSpeech(text);
        } else {
            alert('没有选中文本');
        }
    });

    document.body.appendChild(button);

    // 监听文本选择事件
    document.addEventListener('selectionchange', () => {
        const selection = window.getSelection();
        if (selection.rangeCount > 0) {
            const range = selection.getRangeAt(0);
            const rect = range.getBoundingClientRect();
            if (rect.width > 0 && rect.height > 0) {
                button.style.top = `${rect.bottom + window.scrollY}px`;
                button.style.left = `${rect.right + window.scrollX}px`;
                button.style.display = 'block';
            } else {
                button.style.display = 'none';
            }
        } else {
            button.style.display = 'none';
        }
    });
})();

以下为原始帖子

bob 集成的tts很方便,划句子快捷键调用翻译,点按 扬声器按钮 就可以调用 tts

但是无法调用Open AI的 (或者说我没找到办法)

tts-1

听过tts-1自然人声已经让我回不去微软(bob可调用的那几个声源)的tts了

Lobe 虽然可以调用OpenAI 的 tts,并且可以下载,但是每次需要我:

  1. 把文本复制并粘贴到Lobe窗口后发送(和ai对话)
  2. 然后在自己那段文字的侧边栏点击 语音朗读

非常的麻烦

所以向请教各位佬,是否能够推荐一个应用 /Chrome 拓展/ 插件 可以满足:

能在网页划句后点按/快捷键 快速调用 Open AI tts ?
当然能全局调用并且可以下载音频最好

:heart: 谢谢大家 :smiling_face_with_three_hearts:

15 个赞

搞七捻三快问快答

:saluting_face: 这里怎么连水回复的都没有

不懂 帮顶 :rofl:

2 个赞

:smiling_face_with_three_hearts: 谢谢你

1 个赞

utools 的ai好友插件,翻译后可以调用tts,但是还不支持复制后直接tts

1 个赞

谢谢!已经用4o写了个油猴插件了 :rofl:

牛的,全程都用的gpt写的吗

1 个赞

是的,从python到最终的油猴脚本,对话一共18条。 :heart_eyes:

3 个赞

插件分享给大家看看 :smiling_face_with_three_hearts:
gpt 好啊,真正降低了使用编程的门槛和提高效率

1 个赞

:rofl: 是的,的确如此,大家要大胆造轮子。
我发现因为等级暂时编辑不了帖子,修改了一下发布到油插了,谢谢你对此感兴趣 :smiling_face_with_three_hearts:

1 个赞

行动力真牛,好用!

1 个赞

谢谢鼓励! :smiling_face_with_three_hearts:


佬 求教 为啥我原本的就不能播放 :melting_face:

2 个赞

我刚才测试了一下,也无法播放,目前没有找到原因。可是下面的免费站点里可以流畅朗读。 :rofl:

还真是 :rofl:

1 个赞

行动力很强

3 个赞

最好能直接上传文件,文字太长不想复制

1 个赞

感谢

1 个赞

只能用openAI官方的key吗?我用其他中转的,没法朗读

1 个赞