🏠 Home 

Greasy Fork is available in English.

Google Cloud Text-to-Speech AI Downloader

Add a Download button for Google Cloud Text-to-Speech AI.

// ==UserScript==
// @name         Google Cloud Text-to-Speech AI Downloader
// @description  Add a Download button for Google Cloud Text-to-Speech AI.
// @icon         https://www.google.com/s2/favicons?sz=64&domain=cloud.google.com
// @version      1.0
// @author       afkarxyz
// @namespace    https://github.com/afkarxyz/misc-scripts/
// @supportURL   https://github.com/afkarxyz/misc-scripts/issues
// @license      MIT
// @match        https://www.gstatic.com/cloud-site-ux/text_to_speech/text_to_speech.min.html
// @grant        none
// ==/UserScript==
(function() {
'use strict';
let lastResponse = null;
let lastPayload = null;
const originalOpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url) {
this.customURL = url;
if (url.includes('texttospeech.googleapis.com/v1beta1/text:synthesize')) {
this.addEventListener('readystatechange', function() {
if (this.readyState === 4) {
try {
const response = JSON.parse(this.responseText);
lastResponse = response.audioContent;
} catch (e) {}
}
});
}
originalOpen.apply(this, arguments);
};
const originalSend = XMLHttpRequest.prototype.send;
XMLHttpRequest.prototype.send = function(data) {
if (this.customURL && this.customURL.includes('texttospeech.googleapis.com/v1beta1/text:synthesize')) {
try {
lastPayload = typeof data === 'string' ? JSON.parse(data) : data;
} catch (e) {}
}
originalSend.apply(this, arguments);
};
const base64ToArrayBuffer = base64 => {
const binary = atob(base64);
const buffer = new Uint8Array(binary.length);
for (let i = 0; i < binary.length; i++) {
buffer[i] = binary.charCodeAt(i);
}
return buffer.buffer;
};
const showCustomPopup = (message, type = 'success') => {
const existingPopup = document.getElementById('custom-popup');
if (existingPopup) {
existingPopup.remove();
}
const popup = document.createElement('div');
popup.id = 'custom-popup';
popup.style.cssText = `
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%) translateY(-100%);
z-index: 9999;
background-color: ${type === 'success' ? '#4CAF50' : '#f44336'};
color: white;
padding: 0.938em 1.563em;
border-radius: 0.313em;
opacity: 0;
transition: all 0.2s ease-in-out;
text-align: center;
max-width: 80%;
`;
popup.textContent = message;
document.body.appendChild(popup);
requestAnimationFrame(() => {
popup.style.opacity = '1';
popup.style.transform = 'translate(-50%, -50%)';
});
setTimeout(() => {
popup.style.opacity = '0';
popup.style.transform = 'translate(-50%, -50%) translateY(-100%)';
setTimeout(() => {
popup.remove();
}, 300);
}, 1000);
};
const downloadAudio = () => {
if (!lastResponse || !lastPayload) {
showCustomPopup('Generate audio first!', 'error');
return;
}
const now = new Date();
const timestamp = `${now.getFullYear()}${String(now.getMonth() + 1).padStart(2, '0')}${String(now.getDate()).padStart(2, '0')}_${String(now.getHours()).padStart(2, '0')}${String(now.getMinutes()).padStart(2, '0')}${String(now.getSeconds()).padStart(2, '0')}`;
const truncatedText = lastPayload.input.text.substring(0, 25) + '...';
const filename = `${timestamp}_${lastPayload.voice.name}_${truncatedText}.wav`;
const blob = new Blob([base64ToArrayBuffer(lastResponse)], { type: 'audio/wav' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = filename;
link.click();
URL.revokeObjectURL(link.href);
showCustomPopup('Audio downloaded successfully!');
lastResponse = null;
lastPayload = null;
};
const createDownloadButton = () => {
const button = document.createElement('paper-button');
button.id = 'downloadButton';
button.className = 'state-ready';
button.setAttribute('role', 'button');
button.tabIndex = 0;
const iconSpan = document.createElement('span');
iconSpan.className = 'icon';
iconSpan.style.width = '1.125em';
iconSpan.style.height = '1.125em';
const ironIcon = document.createElement('iron-icon');
ironIcon.setAttribute('icon', 'file-download');
ironIcon.style.width = '1.125em';
ironIcon.style.height = '1.125em';
iconSpan.appendChild(ironIcon);
const labelSpan = document.createElement('span');
labelSpan.className = 'label';
labelSpan.style.paddingLeft = '0.25em';
const readySpan = document.createElement('span');
readySpan.className = 'ready';
readySpan.textContent = 'Download';
labelSpan.appendChild(readySpan);
const buttonInner = document.createElement('span');
buttonInner.className = 'button-inner';
buttonInner.style.padding = '0 0.625em';
buttonInner.appendChild(iconSpan);
buttonInner.appendChild(labelSpan);
button.appendChild(buttonInner);
button.style.marginLeft = '0.625em';
button.style.background = 'var(--google-blue-500)';
button.style.color = '#fff';
button.addEventListener('click', downloadAudio);
return button;
};
const addDownloadButton = () => {
const app = document.querySelector('ts-app');
if (!app) {
return;
}
const shadowRoot = app.shadowRoot;
if (!shadowRoot) {
return;
}
const controlPlayback = shadowRoot.querySelector('.control-playback');
if (!controlPlayback) {
return;
}
if (controlPlayback.querySelector('#downloadButton')) {
return;
}
const existingButton = controlPlayback.querySelector('ts-button');
if (existingButton) {
existingButton.parentNode.insertBefore(createDownloadButton(), existingButton.nextSibling);
}
};
const checkPageReadiness = () => {
const app = document.querySelector('ts-app');
if (app && app.shadowRoot) {
addDownloadButton();
} else {
requestAnimationFrame(checkPageReadiness);
}
};
checkPageReadiness();
const observer = new MutationObserver(addDownloadButton);
observer.observe(document.body, { childList: true, subtree: true });
})();