tts-voice-creator-clone-and.../scripts/run_voice_quality_pass.mjs
mARTin-B78 f7264a8c07 chore: release 1.21.0 - Consistent ComfyUI Character Renders and Audiobook Export
- Deterministic hash-based character portraits and reference sheets
- Implemented strict early Renaissance aesthetic controls and role-specific headwear rules
- Added audiobook batch export scripts and progress tooling
- Verified book profiles with a new 'confirmed' state tracking mechanism
- Updated Pillow dependency for robust image handling
- Configured .gitignore for audiobook and audio cache exports
2026-08-23 09:12:57 +02:00

66 lines
3.6 KiB
JavaScript

import { chromium } from '@playwright/test';
const base = process.env.TTSC_URL || 'http://localhost:7890';
const browser = await chromium.launch({ headless: true, executablePath: process.env.CHROMIUM_PATH || '/snap/bin/chromium' });
const page = await browser.newPage();
page.on('console', msg => { if (msg.type() === 'error') console.error('[browser]', msg.text()); });
await page.goto(base + '/#s-library', { waitUntil: 'domcontentloaded' });
await page.waitForFunction(() => typeof window.clGetAll === 'function' && typeof window._charAutoDesignVoice === 'function', null, { timeout: 30000 });
await page.evaluate(async () => {
if (typeof window.loadVoiceLibrary === 'function') await window.loadVoiceLibrary();
});
const targets = await page.evaluate(async () => {
const books = new Set([
'Hohlbein & Hennen - DSA - Das Jahr des Greifen 01 - Der Sturm',
'Hohlbein & Hennen - DSA - Das Jahr des Greifen 02 - Die Entdeckung',
'Hohlbein & Hennen - DSA - Das Jahr des Greifen 03 - Die Amazone',
]);
const chars = await window.clGetAll();
const voices = window._voices || [];
const ratings = new Map(voices.map(v => [v.id, Number(v.rating || 0)]));
return chars.filter(c => books.has(c.book) && c.voice && ratings.get(typeof c.voice === 'object' ? c.voice.id : c.voice) >= 1 && ratings.get(typeof c.voice === 'object' ? c.voice.id : c.voice) <= 2)
.map(c => {
const voiceId = typeof c.voice === 'object' ? c.voice.id : c.voice;
const voice = voices.find(v => v.id === voiceId) || {};
return { id: c.id, book: c.book, name: c.name, rating: ratings.get(voiceId), feedback: String(voice.note || '').trim() };
});
});
console.log(`Quality pass starting for ${targets.length} voices`);
let done = 0;
for (const target of targets) {
const result = await page.evaluate(async (target) => {
const all = await window.clGetAll();
const rec = all.find(c => c.id === target.id);
if (!rec) throw new Error('Character no longer exists');
const sheet = rec.sheet || {};
const sample = [sheet.physical, sheet.backstory, sheet.motivation].filter(Boolean).join(' ');
const language = typeof window.detectLang === 'function' && sample ? (window.detectLang(sample) || '') : '';
const llm = typeof window.statusLlmTarget === 'function' ? window.statusLlmTarget() : { url: '', model: '' };
const promptResponse = await fetch('/api/character-generate-prompts', {
method: 'POST', headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ name: rec.name, book: rec.book || '', sheet, voice_feedback: target.feedback || '', language, llm_url: llm.url, model: llm.model, fields: ['voice_design_prompt'] }),
});
if (!promptResponse.ok) throw new Error('Prompt generation: ' + promptResponse.status + ' ' + promptResponse.statusText);
const promptData = await promptResponse.json();
const prompt = String(promptData.voice_design_prompt || '').trim();
if (!prompt) throw new Error('Empty regenerated prompt');
rec.sheet = rec.sheet || {};
rec.sheet.voice_design_prompt = prompt;
rec.updated = new Date();
await window.clPut(rec);
await window._charAutoDesignVoice(rec, true, prompt);
return { name: rec.name, prompt };
}, target);
done += 1;
console.log(`[${done}/${targets.length}] ${target.rating}★ ${target.book} — ${result.name}`);
}
console.log('Quality pass complete');
await page.evaluate(async () => {
const r = await fetch('/api/tts/restart', { method: 'POST' });
if (!r.ok) throw new Error('TTS restart failed: ' + r.status);
});
console.log('TTS backend restarted; redesigned audio cache flushed');
await browser.close();