템퍼몽키 확장프로그램 설치:
https://chromewebstore.google.com/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo
이거 나도 방에서 배운 크롬 확장 프로그램인데
원래는 크롬확장을 따로 바닥부터 개발해야 특정사이트에 커스텀 기능을 추가할수있는데
그걸 그냥 스크립트 입력만으로 가능하게 해주는 확장프로그램임
구글 ai studio TTS 에서 프롬프트를 저장하고 불러오는 기능이 없는데 추가할수있음 아래 스크립트 공유함
!!주의점
브라우저 로컬스토리지라는데 저장하기땜에 캐쉬 초기화같은거 하면 날라감 수요있으면 따로 백업 저장할수있게 기능 추가함
우선 위에 템퍼몽키 확장프로그램 설치 링크에서 브라우저에 확장프로그램을 설치
툴바에서 위 모양 아이콘 클릭하면 메뉴가 나오는데
새스크립트 만들기 누르고
이런화면이 나오는데 이글 제일 밑에 있는 스크립트 전문 복사해서 붙여넣고 [파일] -> [저장]으로 저장해놓으면
아래 구글 TTS에서 사용 가능해짐
https://aistudio.google.com/generate-speech
일단 테스트버전이라
Single-spekeraudio에서 만 사용 가능하고
위와같은 방식으로 사용 가능하니까 참고바람
스크립트 전문
// ==UserScript==
// @name Google AI Studio 프리셋 관리
// @namespace http://tampermonkey.net/
// @version 1.0
// @description Google AI Studio 음성 생성 페이지에 프리셋 저장/불러오기 기능 추가
// @author You
// @match https://aistudio.google.com/generate-speech*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// 로컬스토리지 키
const PRESETS_KEY = 'aistudio_voice_presets';
// 프리셋 저장/불러오기 함수들
function getPresets() {
const presets = localStorage.getItem(PRESETS_KEY);
return presets ? JSON.parse(presets) : {};
}
function savePreset(name, content) {
const presets = getPresets();
presets[name] = content;
localStorage.setItem(PRESETS_KEY, JSON.stringify(presets));
}
function deletePreset(name) {
const presets = getPresets();
delete presets[name];
localStorage.setItem(PRESETS_KEY, JSON.stringify(presets));
}
// Style instructions 텍스트에리어 찾기
function getStyleTextarea() {
return document.querySelector('textarea[aria-label="Style instructions"]');
}
// 페이지가 완전히 로드될 때까지 대기
function waitForElement(selector, callback) {
const observer = new MutationObserver((mutations, obs) => {
const element = document.querySelector(selector);
if (element) {
obs.disconnect();
callback(element);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
// 이미 존재하는 경우 즉시 실행
const existingElement = document.querySelector(selector);
if (existingElement) {
callback(existingElement);
}
}
// 셀렉트박스 생성 함수
function createPresetSelect() {
const container = document.createElement('div');
container.style.cssText = `
margin: 10px 0;
padding: 10px;
border: 1px solid #dadce0;
border-radius: 8px;
background-color: #f8f9fa;
`;
const label = document.createElement('label');
label.textContent = '저장된 프리셋:';
label.style.cssText = `
display: block;
margin-bottom: 5px;
font-size: 14px;
font-weight: 500;
color: #3c4043;
`;
const selectContainer = document.createElement('div');
selectContainer.style.cssText = `
display: flex;
gap: 8px;
align-items: stretch;
width: 100%;
`;
const select = document.createElement('select');
select.style.cssText = `
flex: 1;
min-width: 0;
padding: 8px 12px;
border: 1px solid #dadce0;
border-radius: 4px;
background-color: white;
color: #3c4043;
font-size: 14px;
cursor: pointer;
height: 36px;
`;
const loadButton = document.createElement('button');
loadButton.textContent = '불러오기';
loadButton.style.cssText = `
background-color: #1a73e8;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
height: 36px;
white-space: nowrap;
min-width: 70px;
transition: background-color 0.3s;
`;
const deleteButton = document.createElement('button');
deleteButton.textContent = '삭제';
deleteButton.style.cssText = `
background-color: #ea4335;
color: white;
border: none;
padding: 8px 12px;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
height: 36px;
white-space: nowrap;
min-width: 50px;
transition: background-color 0.3s;
`;
// 프리셋 목록 업데이트
function updatePresetList() {
const presets = getPresets();
// 기존 옵션들 모두 제거
while (select.firstChild) {
select.removeChild(select.firstChild);
}
// 기본 옵션 추가
const defaultOption = document.createElement('option');
defaultOption.value = '';
defaultOption.textContent = '-- 프리셋 선택 --';
select.appendChild(defaultOption);
// 프리셋 옵션들 추가
Object.keys(presets).forEach(name => {
const option = document.createElement('option');
option.value = name;
option.textContent = name;
select.appendChild(option);
});
}
// 불러오기 버튼 이벤트
loadButton.onclick = () => {
const selectedPreset = select.value;
if (!selectedPreset) {
alert('불러올 프리셋을 선택해주세요.');
return;
}
const presets = getPresets();
const content = presets[selectedPreset];
const textarea = getStyleTextarea();
if (textarea && content) {
textarea.value = content;
// Angular의 변경 감지를 위해 이벤트 발생
textarea.dispatchEvent(new Event('input', { bubbles: true }));
textarea.dispatchEvent(new Event('change', { bubbles: true }));
alert(`프리셋 "${selectedPreset}"이 불러와졌습니다.`);
} else {
alert('텍스트에리어를 찾을 수 없거나 프리셋 내용이 없습니다.');
}
};
// 삭제 버튼 이벤트
deleteButton.onclick = () => {
const selectedPreset = select.value;
if (!selectedPreset) {
alert('삭제할 프리셋을 선택해주세요.');
return;
}
if (confirm(`프리셋 "${selectedPreset}"을(를) 삭제하시겠습니까?`)) {
deletePreset(selectedPreset);
updatePresetList();
alert(`프리셋 "${selectedPreset}"이 삭제되었습니다.`);
}
};
// 호버 효과
loadButton.onmouseover = () => loadButton.style.backgroundColor = '#1557b0';
loadButton.onmouseout = () => loadButton.style.backgroundColor = '#1a73e8';
deleteButton.onmouseover = () => deleteButton.style.backgroundColor = '#d33b2c';
deleteButton.onmouseout = () => deleteButton.style.backgroundColor = '#ea4335';
selectContainer.appendChild(select);
selectContainer.appendChild(loadButton);
selectContainer.appendChild(deleteButton);
container.appendChild(label);
container.appendChild(selectContainer);
// 초기 프리셋 목록 로드
updatePresetList();
// 전역에서 접근 가능하도록 함수 저장
container.updatePresetList = updatePresetList;
return container;
}
// 저장 버튼 생성 함수
function createSaveButton() {
const button = document.createElement('button');
button.textContent = '프리셋 저장';
button.style.cssText = `
background-color: #34a853;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
margin: 10px 0;
font-size: 14px;
transition: background-color 0.3s;
`;
// 호버 효과
button.onmouseover = () => button.style.backgroundColor = '#2d8f45';
button.onmouseout = () => button.style.backgroundColor = '#34a853';
// 클릭 이벤트
button.onclick = () => {
const textarea = getStyleTextarea();
if (!textarea) {
alert('Style instructions 텍스트에리어를 찾을 수 없습니다.');
return;
}
const content = textarea.value.trim();
if (!content) {
alert('저장할 내용이 없습니다.');
return;
}
const presetName = prompt('프리셋 이름을 입력해주세요:');
if (!presetName || !presetName.trim()) {
return;
}
const trimmedName = presetName.trim();
const existingPresets = getPresets();
if (existingPresets[trimmedName]) {
if (!confirm(`프리셋 "${trimmedName}"이 이미 존재합니다. 덮어쓰시겠습니까?`)) {
return;
}
}
savePreset(trimmedName, content);
alert(`프리셋 "${trimmedName}"이 저장되었습니다.`);
// 셀렉트박스 업데이트
const selectContainer = document.querySelector('.preset-select-container');
if (selectContainer && selectContainer.updatePresetList) {
selectContainer.updatePresetList();
}
};
return button;
}
// .settings-content에 셀렉트박스 추가
waitForElement('.settings-content', (settingsContent) => {
console.log('settings-content 요소를 찾았습니다!');
// 이미 추가되었는지 확인
if (!settingsContent.querySelector('.preset-select-container')) {
const presetSelect = createPresetSelect();
presetSelect.classList.add('preset-select-container');
// settings-content의 첫 번째 자식으로 추가
settingsContent.insertBefore(presetSelect, settingsContent.firstChild);
console.log('프리셋 셀렉트박스가 추가되었습니다!');
}
});
// .single-speaker-prompt-builder-wrapper에 저장 버튼 추가
waitForElement('.single-speaker-prompt-builder-wrapper.ng-star-inserted', (wrapper) => {
console.log('single-speaker-prompt-builder-wrapper 요소를 찾았습니다!');
// 이미 추가되었는지 확인
if (!wrapper.querySelector('.preset-save-button')) {
const saveButton = createSaveButton();
saveButton.classList.add('preset-save-button');
// wrapper의 첫 번째 자식으로 추가
wrapper.insertBefore(saveButton, wrapper.firstChild);
console.log('저장 버튼이 추가되었습니다!');
}
});
// 페이지 변경 감지 (SPA 대응)
let currentUrl = location.href;
const urlObserver = new MutationObserver(() => {
if (location.href !== currentUrl) {
currentUrl = location.href;
// URL이 변경되면 다시 요소 추가 시도
setTimeout(() => {
waitForElement('.settings-content', (settingsContent) => {
if (!settingsContent.querySelector('.preset-select-container')) {
const presetSelect = createPresetSelect();
presetSelect.classList.add('preset-select-container');
settingsContent.insertBefore(presetSelect, settingsContent.firstChild);
}
});
waitForElement('.single-speaker-prompt-builder-wrapper.ng-star-inserted', (wrapper) => {
if (!wrapper.querySelector('.preset-save-button')) {
const saveButton = createSaveButton();
saveButton.classList.add('preset-save-button');
wrapper.insertBefore(saveButton, wrapper.firstChild);
}
});
}, 1000);
}
});
urlObserver.observe(document.body, {
childList: true,
subtree: true
});
})();