AI Studio to VOICEVOX
通称:スタ自ボ(Google AI スタジオ自動ボイスボックス読み上げ)
目的:AI Studioで自動音声読み上げ(VOICEVOX)が欲しい人向け
機能:読み上げ話者変更・回答後に自動で音声を読み上げるON/OFF・音量&速度調整
使用手順:①Tampermonkeyのプラグインを インストール
②「スタ自ボインストール」を押して、スクリプトインストールを行う(またはTampermonkeyで、以下のコードを新規保存)
③VOICEVOXを起動する→https://voicevox.hiroshiba.jp/
④Google Studio AIを起動して読み上げできるのかプレイしてみてね
うまくいくと以下の様に自動で読み上げが始まります

Tampermonkeyが良く分からない人はコチラの下部の解説を参考に
詳しくは下部
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | // ==UserScript==
// @name AI Studio to VOICEVOX (v5.3.1 安定版)
// @namespace https://greasyfork.org/scripts/555209/
// @version 5.3.1
// @description AI Studioの回答をVOICEVOXで自動読み上げ (画像URLの読み上げバグを完全修正)
// @author FoeverPWA
// @match https://aistudio.google.com/*
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @grant GM_registerMenuCommand
// @connect localhost
// @license MIT
// @downloadURL https://update.greasyfork.org/scripts/555209/AI%20Studio%20to%20VOICEVOX.user.js
// @updateURL https://update.greasyfork.org/scripts/555209/AI%20Studio%20to%20VOICEVOX.meta.js
// ==/UserScript==
(function() {
'use strict';
// ===== 設定 =====
const CONFIG_KEY = 'aistudio_voicevox_config';
const defaultConfig = {
apiUrl: 'http://localhost:50021',
speakerId: 20,
autoPlay: true,
speedScale: 1.0,
volumeScale: 1.0
};
let savedConfig = GM_getValue(CONFIG_KEY, {});
let config = Object.assign({}, defaultConfig, savedConfig);
// ===== 状態管理 =====
let state = {
isPlaying: false,
isSynthesizing: false,
currentAudio: null,
audioQueue: [],
textQueue: [],
lastProcessedText: '',
lastProcessedCleanText: '',
textBuffer: '', // 【重要】読み上げ前のテキストバッファ
wasGenerating: false, // 前回の生成状態を保持
playButton: null,
shouldStop: false
};
// ===== ユーティリティ関数 =====
function getModelResponseText() {
const turns = document.querySelectorAll('ms-chat-turn');
const lastTurn = turns[turns.length - 1];
if (!lastTurn) return '';
const container = lastTurn.querySelector('[data-turn-role="Model"]');
if (!container) return '';
const textChunks = Array.from(container.querySelectorAll('ms-text-chunk'));
let text = '';
textChunks.forEach(chunk => {
if (chunk.closest('ms-code-chunk, ms-thought-chunk')) return;
text += chunk.textContent;
});
return text;
}
function isGenerating() {
const turns = document.querySelectorAll('ms-chat-turn');
const lastTurn = turns[turns.length - 1];
if (!lastTurn) return false;
return !lastTurn.querySelector('button[iconname="thumb_up"]');
}
function cleanMarkdown(text) {
if (!text) return '';
return text
.replace(/!\[.*?\]\(.*?\)/g, '') // 完成した画像マークダウンを除去
.replace(/\[([^\]]+)\]\([^\)]+\)/g, '$1')
.replace(/```[\s\S]*?```/g, '')
.replace(/`[^`]+`/g, '')
.replace(/(\*\*|__)(.*?)\1/g, '$2')
.replace(/(\*|_)(.*?)\1/g, '$2')
.replace(/^#+\s+/gm, '')
.replace(/^[\-\*]{3,}$/gm, '')
.replace(/^\s*[\-\*\+]\s+/gm, '')
.replace(/^\s*\d+\.\s+/gm, '')
.replace(/^>\s+/gm, '')
.replace(/\n{3,}/g, '\n\n')
.trim();
}
function splitIntoSentences(text) {
return text.split(/(?<=[。!?\n])|(?<=\.\s)/g)
.map(s => s.trim())
.filter(s => s.length > 0);
}
// ===== VOICEVOX API (タイムアウト付き) =====
function synthesizeSpeech(text, callback, customApiUrl = null, customSpeakerId = null, customSpeedScale = null, customVolumeScale = null) {
const apiUrl = customApiUrl || config.apiUrl;
const speakerId = customSpeakerId !== null ? customSpeakerId : config.speakerId;
const speedScale = customSpeedScale !== null ? customSpeedScale : config.speedScale;
const volumeScale = customVolumeScale !== null ? customVolumeScale : config.volumeScale;
const queryUrl = `${apiUrl}/audio_query?text=${encodeURIComponent(text)}&speaker=${speakerId}`;
GM_xmlhttpRequest({
method: 'POST', url: queryUrl, timeout: 5000,
onload: (response) => {
if (response.status !== 200) return callback(null);
try {
const audioQuery = JSON.parse(response.responseText);
audioQuery.speedScale = speedScale; audioQuery.volumeScale = volumeScale;
GM_xmlhttpRequest({
method: 'POST', url: `${apiUrl}/synthesis?speaker=${speakerId}`,
headers: { 'Content-Type': 'application/json' }, data: JSON.stringify(audioQuery),
responseType: 'blob', timeout: 10000,
onload: (synthResponse) => callback(synthResponse.status === 200 ? URL.createObjectURL(synthResponse.response) : null),
onerror: () => callback(null), ontimeout: () => callback(null)
});
} catch (e) { callback(null); }
},
onerror: () => callback(null), ontimeout: () => callback(null)
});
}
// ===== 再生制御 =====
function playNext() {
if (state.shouldStop || state.isPlaying || state.audioQueue.length === 0) {
if (!state.isSynthesizing && state.audioQueue.length === 0 && state.textQueue.length === 0) {
state.isPlaying = false;
updatePlayButton(false);
}
return;
}
state.isPlaying = true; updatePlayButton(true);
const audioUrl = state.audioQueue.shift();
const audio = new Audio(audioUrl);
state.currentAudio = audio;
audio.onended = audio.onerror = () => {
URL.revokeObjectURL(audioUrl);
state.isPlaying = false; state.currentAudio = null;
if (!state.shouldStop) playNext();
};
audio.play().catch(() => { state.isPlaying = false; state.currentAudio = null; });
}
function processTextQueue() {
if (state.shouldStop || state.isSynthesizing || state.textQueue.length === 0) return;
state.isSynthesizing = true;
const text = state.textQueue.shift();
synthesizeSpeech(text, (audioUrl) => {
state.isSynthesizing = false;
if (state.shouldStop) { if (audioUrl) URL.revokeObjectURL(audioUrl); return; }
if (audioUrl) { state.audioQueue.push(audioUrl); playNext(); }
if (state.textQueue.length > 0) setTimeout(() => processTextQueue(), 100);
});
}
function startPlayback(text) {
stopPlayback();
state.shouldStop = false;
const cleanedText = cleanMarkdown(text);
const sentences = splitIntoSentences(cleanedText);
if (sentences.length === 0) return;
state.textQueue = sentences;
state.lastProcessedText = text;
state.lastProcessedCleanText = cleanedText;
updatePlayButton(true);
processTextQueue();
}
function addToPlaybackQueue(newCleanText) {
if (state.shouldStop || !newCleanText) return;
const sentences = splitIntoSentences(newCleanText);
if (sentences.length === 0) return;
const wasIdle = !state.isPlaying && !state.isSynthesizing && state.textQueue.length === 0;
state.textQueue.push(...sentences);
if (wasIdle) { updatePlayButton(true); processTextQueue(); }
else if (!state.isSynthesizing) { processTextQueue(); }
}
function stopPlayback() {
state.shouldStop = true;
if (state.currentAudio) {
state.currentAudio.pause(); state.currentAudio.src = ''; state.currentAudio = null;
}
state.audioQueue.forEach(url => URL.revokeObjectURL(url));
state.audioQueue = [];
state.textQueue = [];
state.textBuffer = '';
state.isPlaying = false;
state.isSynthesizing = false;
updatePlayButton(false);
}
// ===== UI制御とメインロジック =====
function updatePlayButton(isActive) {
if (state.playButton) {
state.playButton.textContent = isActive ? '■ 停止' : '🔊 再生';
}
}
let debounceTimer = null;
function handleUpdate() {
if (debounceTimer) return;
debounceTimer = setTimeout(() => {
debounceTimer = null;
try {
const isCurrentlyGenerating = isGenerating();
// 新しい回答の開始を検知(false -> true)
if (isCurrentlyGenerating && !state.wasGenerating) {
stopPlayback();
state.lastProcessedText = '';
state.lastProcessedCleanText = '';
state.shouldStop = false;
}
const currentText = getModelResponseText();
if (config.autoPlay && !state.shouldStop && currentText) {
const cleanedFullText = cleanMarkdown(currentText);
// 差分があればバッファに追加
if (cleanedFullText.length > state.lastProcessedCleanText.length) {
const newCleanTextPart = cleanedFullText.substring(state.lastProcessedCleanText.length);
state.lastProcessedText = currentText;
state.lastProcessedCleanText = cleanedFullText;
state.textBuffer += newCleanTextPart;
}
}
// バッファから完成した文を切り出してキューに追加
const sentenceRegex = /(?<=[。!?\n])/;
if (sentenceRegex.test(state.textBuffer)) {
let parts = state.textBuffer.split(sentenceRegex);
const lastPart = parts[parts.length - 1] === '' ? '' : parts.pop();
const sentencesToQueue = parts.join('');
if (sentencesToQueue) {
addToPlaybackQueue(sentencesToQueue);
}
state.textBuffer = lastPart || '';
}
// 生成が完了した瞬間(true -> false)にバッファの残りを処理
if (state.wasGenerating && !isCurrentlyGenerating) {
if (state.textBuffer.trim().length > 0) {
addToPlaybackQueue(state.textBuffer);
state.textBuffer = '';
}
}
state.wasGenerating = isCurrentlyGenerating;
// 再生ボタンの追加/更新
const lastTurn = document.querySelector('ms-chat-turn:last-of-type');
if (lastTurn && !lastTurn.querySelector('#voicevox_play_button')) {
const footer = lastTurn.querySelector('.turn-footer');
if (footer) {
const button = document.createElement('button');
button.id = 'voicevox_play_button';
button.textContent = '🔊 再生';
button.style.cssText = `padding: 5px 12px; background: rgb(138, 180, 248); color: rgb(32, 33, 36); border: none; border-radius: 20px; cursor: pointer; margin-left: 8px; font-weight: 500;`;
button.onclick = () => {
const isActive = state.isPlaying || state.isSynthesizing || state.textQueue.length > 0;
if (isActive) { stopPlayback(); }
else { startPlayback(getModelResponseText()); }
};
footer.insertBefore(button, footer.firstChild);
state.playButton = button;
}
}
if (state.playButton) {
updatePlayButton(state.isPlaying || state.isSynthesizing || state.textQueue.length > 0);
}
} catch (e) {
console.warn("AI Studio to VOICEVOX: handleUpdate error:", e);
}
}, 300);
}
// ===== DOM監視 =====
const observer = new MutationObserver(handleUpdate);
observer.observe(document.body, { childList: true, subtree: true });
// ===== 設定画面(変更なし) =====
let speakerList = [];
function fetchSpeakers(callback) {
GM_xmlhttpRequest({
method: 'GET', url: `${config.apiUrl}/speakers`, timeout: 3000,
onload: (response) => {
if (response.status === 200) {
try { speakerList = JSON.parse(response.responseText); callback(true); }
catch (e) { callback(false); }
} else { callback(false); }
},
onerror: () => callback(false), ontimeout: () => callback(false)
});
}
function getSpeakerName(speakerId) {
for (const speaker of speakerList) {
for (const style of speaker.styles) {
if (style.id === speakerId) return `${speaker.name} (${style.name})`;
}
}
return `ID: ${speakerId} (不明)`;
}
function openSettings() {
const overlay = document.createElement('div');
overlay.style.cssText = `position:fixed;top:0;left:0;right:0;bottom:0;background:rgba(0,0,0,0.7);z-index:10000;display:flex;align-items:center;justify-content:center;`;
const panel = document.createElement('div');
panel.style.cssText = `background:#202124;color:#e8eaed;padding:24px;border-radius:8px;width:500px;max-height:90vh;overflow-y:auto;`;
function createStyledElement(tag, style, textContent = '') {
const el = document.createElement(tag);
el.style.cssText = style;
if (textContent) el.textContent = textContent;
return el;
}
panel.appendChild(createStyledElement('h2', 'margin:0 0 20px;font-size:1.5em;', '🔊 VOICEVOX連携設定'));
const apiUrlSection = createStyledElement('div', 'margin-bottom:16px;');
apiUrlSection.appendChild(createStyledElement('label', 'display:block;margin-bottom:8px;color:#9aa0a6;', 'API URL:'));
const apiUrlInput = createStyledElement('input', 'width:100%;padding:10px;background:#3c4043;color:#e8eaed;border:1px solid #5f6368;border-radius:4px;box-sizing:border-box;font-size:14px;');
apiUrlInput.type = 'text'; apiUrlInput.value = config.apiUrl;
apiUrlSection.appendChild(apiUrlInput); panel.appendChild(apiUrlSection);
const speakerSection = createStyledElement('div', 'margin-bottom:16px;position:relative;');
speakerSection.appendChild(createStyledElement('label', 'display:block;margin-bottom:8px;color:#9aa0a6;', 'スピーカー:'));
const speakerInputContainer = createStyledElement('div', 'display:flex;gap:10px;align-items:center;');
const speakerIdInput = createStyledElement('input', 'width:100px;padding:10px;background:#3c4043;color:#e8eaed;border:1px solid #5f6368;border-radius:4px;box-sizing:border-box;font-size:14px;');
speakerIdInput.type = 'number'; speakerIdInput.value = config.speakerId;
const speakerNameDisplay = createStyledElement('span', 'color:#81c995;font-weight:500;');
const speakerListContainer = createStyledElement('div', 'position:absolute;top:100%;left:0;width:100%;z-index:10001;max-height:200px;overflow-y:auto;background:#3c4043;border:1px solid #5f6368;border-radius:4px;display:none;');
speakerInputContainer.appendChild(speakerIdInput); speakerInputContainer.appendChild(speakerNameDisplay);
speakerSection.appendChild(speakerInputContainer); speakerSection.appendChild(speakerListContainer);
panel.appendChild(speakerSection);
const speedSection = createStyledElement('div', 'margin-bottom:16px;');
speedSection.appendChild(createStyledElement('label', 'display:block;margin-bottom:8px;color:#9aa0a6;', '読み上げ速度:'));
const speedContainer = createStyledElement('div', 'display:flex;gap:12px;align-items:center;');
const speedSlider = createStyledElement('input', 'flex:1;');
speedSlider.type = 'range'; speedSlider.min = 0.5; speedSlider.max = 2.0; speedSlider.step = 0.1; speedSlider.value = config.speedScale;
const speedValue = createStyledElement('span', 'min-width:50px;color:#8ab4f8;font-weight:500;text-align:right;', `${config.speedScale.toFixed(1)}x`);
speedContainer.appendChild(speedSlider); speedContainer.appendChild(speedValue);
speedSection.appendChild(speedContainer); panel.appendChild(speedSection);
const volumeSection = createStyledElement('div', 'margin-bottom:16px;');
volumeSection.appendChild(createStyledElement('label', 'display:block;margin-bottom:8px;color:#9aa0a6;', '音量:'));
const volumeContainer = createStyledElement('div', 'display:flex;gap:12px;align-items:center;');
const volumeSlider = createStyledElement('input', 'flex:1;');
volumeSlider.type = 'range'; volumeSlider.min = 0.0; volumeSlider.max = 2.0; volumeSlider.step = 0.1; volumeSlider.value = config.volumeScale;
const volumeValue = createStyledElement('span', 'min-width:50px;color:#8ab4f8;font-weight:500;text-align:right;', `${Math.round(config.volumeScale * 100)}%`);
volumeContainer.appendChild(volumeSlider); volumeContainer.appendChild(volumeValue);
volumeSection.appendChild(volumeContainer); panel.appendChild(volumeSection);
const testSection = createStyledElement('div', 'margin:16px 0;padding-top:16px;border-top:1px solid #3c4043;');
const testButton = createStyledElement('button', 'width:100%;padding:10px;background:#81c995;color:#202124;border:none;border-radius:4px;cursor:pointer;font-weight:500;', '🎵 サンプル音声を再生');
testSection.appendChild(testButton); panel.appendChild(testSection);
const autoplaySection = createStyledElement('div', 'margin-bottom:20px;padding-top:16px;border-top:1px solid #3c4043;');
const autoplayLabel = createStyledElement('label', 'display:flex;align-items:center;cursor:pointer;');
const autoplayCheckbox = createStyledElement('input', 'margin-right:12px;width:20px;height:20px;');
autoplayCheckbox.type = 'checkbox'; autoplayCheckbox.checked = config.autoPlay;
autoplayLabel.appendChild(autoplayCheckbox);
autoplayLabel.appendChild(createStyledElement('span', '', '自動再生を有効にする'));
autoplaySection.appendChild(autoplayLabel); panel.appendChild(autoplaySection);
const actionsSection = createStyledElement('div', 'display:flex;justify-content:flex-end;gap:10px;padding-top:16px;border-top:1px solid #3c4043;');
const cancelButton = createStyledElement('button', 'padding:10px 20px;background:transparent;color:#8ab4f8;border:1px solid #5f6368;border-radius:4px;cursor:pointer;', 'キャンセル');
const saveButton = createStyledElement('button', 'padding:10px 20px;background:#8ab4f8;color:#202124;border:none;border-radius:4px;cursor:pointer;', '保存');
actionsSection.appendChild(cancelButton); actionsSection.appendChild(saveButton);
panel.appendChild(actionsSection);
document.body.appendChild(overlay); overlay.appendChild(panel);
let testAudio = null;
const closePanel = () => { if (testAudio) { testAudio.pause(); } overlay.remove(); };
speedSlider.oninput = () => speedValue.textContent = `${parseFloat(speedSlider.value).toFixed(1)}x`;
volumeSlider.oninput = () => volumeValue.textContent = `${Math.round(parseFloat(volumeSlider.value) * 100)}%`;
cancelButton.onclick = closePanel;
overlay.onclick = (e) => { if (e.target === overlay) closePanel(); };
document.addEventListener('click', (e) => { if (!speakerSection.contains(e.target)) speakerListContainer.style.display = 'none'; }, true);
const updateSpeakerDisplay = () => {
const id = parseInt(speakerIdInput.value);
speakerNameDisplay.textContent = speakerList.length === 0 ? '取得中...' : getSpeakerName(id);
speakerNameDisplay.style.color = speakerNameDisplay.textContent.includes('不明') ? '#f28b82' : '#81c995';
};
const populateSpeakerList = () => {
if (speakerList.length === 0) { speakerListContainer.innerHTML = `<div style="padding:10px;color:#9aa0a6;">スピーカー情報なし</div>`; return; }
speakerListContainer.innerHTML = speakerList.map(sp => sp.styles.map(st =>
`<div data-id="${st.id}" style="padding:10px;cursor:pointer;border-bottom:1px solid #5f6368;"><div>${sp.name} (${st.name})</div></div>`
).join('')).join('');
speakerListContainer.querySelectorAll('[data-id]').forEach(item => {
item.onclick = () => { speakerIdInput.value = item.dataset.id; updateSpeakerDisplay(); speakerListContainer.style.display = 'none'; };
item.onmouseenter = () => item.style.backgroundColor = '#5f6368';
item.onmouseleave = () => item.style.backgroundColor = '';
});
};
const refreshSpeakers = () => {
speakerList = []; updateSpeakerDisplay();
fetchSpeakers(success => {
if (success) { populateSpeakerList(); }
else { speakerNameDisplay.textContent = 'API接続エラー'; speakerNameDisplay.style.color = '#f28b82'; }
updateSpeakerDisplay();
});
};
speakerIdInput.onfocus = () => speakerListContainer.style.display = 'block';
speakerIdInput.oninput = updateSpeakerDisplay;
apiUrlInput.onchange = refreshSpeakers;
testButton.onclick = () => {
if (testAudio && !testAudio.paused) { testAudio.pause(); testButton.textContent = '🎵 サンプル音声を再生'; return; }
testButton.textContent = '合成中...'; testButton.disabled = true;
synthesizeSpeech('こんにちは。これはサンプル音声です。', url => {
testButton.disabled = false;
if (url) {
testAudio = new Audio(url); testAudio.play(); testButton.textContent = '再生中... ■停止';
testAudio.onended = () => { URL.revokeObjectURL(url); testButton.textContent = '🎵 サンプル音声を再生'; };
} else { testButton.textContent = '再生失敗'; }
}, apiUrlInput.value, parseInt(speakerIdInput.value), parseFloat(speedSlider.value), parseFloat(volumeSlider.value));
};
saveButton.onclick = () => {
config.apiUrl = apiUrlInput.value.trim();
config.speakerId = parseInt(speakerIdInput.value);
config.autoPlay = autoplayCheckbox.checked;
config.speedScale = parseFloat(speedSlider.value);
config.volumeScale = parseFloat(volumeSlider.value);
GM_setValue(CONFIG_KEY, config);
closePanel();
};
refreshSpeakers();
}
// ===== 初期化 =====
GM_registerMenuCommand('VOICEVOX設定', openSettings);
setTimeout(handleUpdate, 2000);
})();
|
AI Studio VOICEVOX Reader 設定マニュアル
このマニュアルでは、AI Studioの回答をVOICEVOXで読み上げるためのスクリプト設定について説明します。
(何も設定しなくてもご利用になれます)
1. 設定画面の開き方
この設定は、AI Studioのページ上ではなく、ブラウザのTampermonkey拡張機能メニューから開きます。
- ブラウザの右上にあるTampermonkeyのアイコンをクリックします。
- 表示されたメニューの中から「VOICEVOX設定」という項目をクリックしてください。

クリックすると、画面の中央に設定パネルが表示されます。
2. 各項目の説明
設定パネルには以下の項目があります。
【1】API URL
- 概要:
あなたのPCで起動しているVOICEVOX ENGINEの場所(アドレス)を指定します。 - 詳細:
通常、VOICEVOXを自分のPCで起動している場合、この値はhttp://localhost:50021のままで問題ありません。変更する必要はほとんどありません。
もし異なるPCや設定でVOICEVOX ENGINEを動かしている場合のみ、そのアドレスに合わせて変更してください。
【2】スピーカー
- 概要:
読み上げに使用するVOICEVOXのキャラクター(話者)とスタイルを選択します。 - 使い方:
- 入力欄をクリックすると、現在利用可能なすべてのスピーカーとスタイルが一覧で表示されます。
- 一覧から使用したいキャラクター名とスタイル名(例:
四国めたん (ノーマル))をクリックして選択します。 - 選択すると、自動的にIDが入力され、右側に緑色で名前が表示されます。
- 補足:
- 緑色の文字: スピーカーが正常に認識されています。
- 赤色の文字: そのIDのスピーカーが見つかりません。IDが間違っているか、API URLへの接続に失敗しています。
【3】サンプル音声を再生
- 概要:
現在のAPI URLとスピーカーIDの設定が正しいか、テストするためのボタンです。 - 使い方:
- API URLとスピーカーを設定した後、このボタンをクリックします。
- 「こんにちは。これはサンプル音声です。」という音声が再生されれば、設定は成功です。
- もし再生されない場合:
- PCでVOICEVOXが起動しているか確認してください。
- API URLが正しいか確認してください。
- スピーカーIDが有効なものか確認してください。
【4】自動再生を有効にする
- 概要:
AI Studioが回答を生成し終えた後、自動的に読み上げを開始するかどうかを設定します。 - 使い方:
- チェックを入れる (有効): 回答が完了すると、自動で読み上げが始まります。
- チェックを外す (無効): 自動では読み上げません。回答の下に表示される「🔊 再生」ボタンを手動でクリックした場合のみ読み上げます。
【5】キャンセル / 保存 ボタン
- キャンセル:
ここで行った変更を一切保存せずに設定画面を閉じます。 - 保存:
ここで行った変更を保存して、設定をスクリプトに反映させます。設定を変更した場合は、必ずこのボタンを押してください。
3. おすすめの設定の流れ
初めて設定を行う際は、以下の手順で行うとスムーズです。
- PCでVOICEVOXアプリケーションを起動しておきます。
- Tampermonkeyメニューから「VOICEVOX設定」を開きます。
- 「API URL」が
http://localhost:50021になっていることを確認します。 - 「スピーカー」の入力欄をクリックし、お好みのキャラクターを選択します。
- 「🎵 サンプル音声を再生」ボタンを押し、音声が聞こえることを確認します。
- 「自動再生」のオン/オフを好みに合わせて設定します。
- 最後に「保存」ボタンを押して設定を完了します。
以上で設定は完了です。快適で"健全"な読み上げをお楽しみください。
個人の感想
もち子さん(ノーマル) デフォルト設定。もはやこれで十分
猫使ビィ(ノーマル) □リ声だがライトに
猫使ビィ(人見知り) □リ声だがウェットに
四国めたん(ささやき) まさにASMR?!
四国めたん(ヒソヒソ) ささやきよりマイルド
ずんだもん(ノーマル) ネタ
ずんだもん(ささやき) ……あれ、意外と
あとナースロボ_タイプTも内緒話があってよい。
あくまで健全な使用をお願いします!
結論から言うと、ずんだもんやVOICEVOXの音声で「エロ音声」を作って公開すると、
主に「法律上のリスク」と「規約違反によるペナルティ」の2種類のリスクがありえます!!
1. 法律上の話(刑事罰になるパターン)
これは「ずんだもん/VOICEVOXだから」というより、「中身が違法かどうか」で決まる部分。
- 成人向けの表現でも、露骨でわいせつと評価される音声を不特定多数に配布・販売すると、「わいせつ電磁的記録等頒布罪」(刑法175条)に当たる可能性がある。公開・販売する側が対象で、視聴する側は通常この罪には当たらない。
- 未成年を性的対象にした内容(年齢を明示していないが明らかに児童と認識されるキャラなど)になると、「児童ポルノ禁止法」絡みで、制作・頒布はもちろん、ダウンロードや所持も処罰対象になり得る。
- さらに、違法アップロードされた有料音声をダウンロードすると、著作権法上の違法ダウンロードとして別途罰則の対象になり得る。
2. VOICEVOX本体の利用規約
- VOICEVOX自体は「商用・非商用問わず利用可能」だが、「作成された音声は各キャラごとの音声ライブラリの規約に従うこと」とされている。
- VOICEVOX関連の解説サイトでは、「公式ガイドラインでR-18コンテンツ(エロ/グロ)は禁止」と明記されているとされ、アダルト用途での利用は規約違反になると整理されている。
- つまり「ソフトとしては使えるけど、R-18用途はライブラリ(キャラ)の規約でNG」という形なので、エロ音声用途は“利用停止・削除要請などのリスク”がある。
3. ずんだもん・東北ずん子系ガイドライン
- ずんだもん周りは、「東北ずん子ずんだもんプロジェクト」のキャラクター利用ガイドラインや、「ZUNDAMON Major Debut EP 二次創作ガイドライン」などがあり、公序良俗に反する利用やイメージを大きく毀損する利用を禁止している。
- ピクシブ百科などの解説でも、ずんだもんの過度な暴力・政治・陰謀論利用等はガイドライン違反のリスクが高いとされ、性的な二次創作も「運営に見つかればNGになり得る」というスタンスが紹介されている。
- 公式が「R-18全面OK」としているわけではなく、“健全な利用前提”でガイドラインが組まれているため、露骨なエロ音声公開は「キャラ規約違反→削除要請・使用禁止要請」という方向のリスクがある。
4. 「個人で聞く」場合と「公開する」場合
- 自分のPC内でこっそりテキストを入れて出力させ、自分だけで聞くぶんについては、通常は「規約違反かどうか」は運営に把握されにくく、刑事罰にも直結しない。
- しかし、その音声を
- 動画サイトや音声配信サイトで公開する
- 有料で販売する
- 不特定多数に配布する
となると、 - VOICEVOX・キャラ側の利用規約違反
- 場合によってはわいせつ物頒布罪・児童ポルノ関連法違反
になり得るので、ここから先はかなりグレー~アウトゾーンになる。
5. 実際にあり得るペナルティ
- 規約違反レベル
- 作品削除要請
- 関連サービスでのアカウントBAN
- キャラクター・音源の使用停止要請
などの「民間サービス上のペナルティ」が中心。
- 法律レベル
- わいせつ物頒布等罪での摘発(2年以下の拘禁刑や罰金など)。
- 児童ポルノ関連での摘発(制作・頒布・所持ともに重い刑罰)。
- 著作権法違反(違法ダウンロードや無断販売等)。
6. 実務的な注意点(グレーに踏み込みたくない場合)
- VOICEVOX・ずんだもんでの性表現は避ける(VOICEVOX関連の有志まとめでも「R18利用はNG」と整理されている)。
- どうしても大人向け音声を作りたい場合は、最初からR18利用を許可している別の音源や、自前で収録した声を使う。
- 公開する場合は、
- 成人向けであることを明記し
- 18歳未満がアクセスできない設定にし
- キャラや音源の利用規約をあらためて確認する
ことが重要になる。
7. 法律相談について
- ここでの説明は一般的な情報であり、具体的に「この作品はセーフかアウトか」を判定することはできない。
- 不安な場合は、IT・著作権・刑事事件に詳しい弁護士に、具体的な内容(台本・公開方法・対象サービスなど)を見せた上で相談するのが安全。
ざっくり言うと、「ずんだもんやVOICEVOXでエロ音声を作ってネットに公開するのは、規約的にはほぼNGで、内容次第では法律的にもアウトになり得る」という感じ。
その他のツール
Google AI Studio に直接画像を出したい!
通称:MIR(スタ画)
https://rentry.co/3bnuvgwu
Wikiのプロンプトまとめ等をみながら、プロンプトだけを即コピーしたい人用に作成しました
通称:即コピ
https://rentry.co/8772bcnh