Converts SRT subtitles into a Polish lektor-style voiceover using a local
ComfyUI instance running Higgs Audio V3, then muxes the result back into the
source video as a second audio track. Video and original audio are always
stream-copied; only the new voiceover track is encoded.
Contents:
lektor_ai_comfy.py main pipeline (standard library only, needs ffmpeg)
lektor_ai_comfy_v2.py batched-context experiment (timing unresolved)
lektor_ai.py earlier version using the hosted Boson AI API
install_comfyui.sh reproduces the ComfyUI environment, versions pinned
docs/ requirements, installation, usage, architecture,
troubleshooting
gudowski/ voice references (.wav) with their transcripts (.txt)
Source media and generated video are gitignored.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
58 lines
2.2 KiB
Python
58 lines
2.2 KiB
Python
#!/usr/bin/env python3
|
|
"""Inspect voice picker and Text mode on AI Studio TTS page."""
|
|
import json, time
|
|
from pathlib import Path
|
|
from playwright.sync_api import sync_playwright
|
|
|
|
profile_dir = Path.home() / ".config" / "lektor_ai" / "browser_profile"
|
|
|
|
def dump_elements(page, label):
|
|
elements = page.evaluate("""() => {
|
|
const sel = 'input, textarea, select, [role="combobox"], [role="listbox"], [role="textbox"], [role="option"], button, [contenteditable]';
|
|
return [...document.querySelectorAll(sel)].map(el => ({
|
|
tag: el.tagName,
|
|
role: el.getAttribute('role') || '',
|
|
ariaLabel: el.getAttribute('aria-label') || '',
|
|
placeholder: el.getAttribute('placeholder') || '',
|
|
class: el.className?.toString().slice(0, 100) || '',
|
|
text: (el.innerText || el.value || '').slice(0, 80),
|
|
}));
|
|
}""")
|
|
path = f"output/elements_{label}.json"
|
|
Path(path).write_text(json.dumps(elements, indent=2, ensure_ascii=False))
|
|
print(f"[{label}] {len(elements)} elements → {path}")
|
|
|
|
with sync_playwright() as pw:
|
|
ctx = pw.chromium.launch_persistent_context(
|
|
str(profile_dir),
|
|
channel="chrome",
|
|
headless=False,
|
|
viewport={"width": 1400, "height": 900},
|
|
ignore_default_args=["--enable-automation"],
|
|
args=["--disable-blink-features=AutomationControlled", "--start-maximized"],
|
|
)
|
|
page = ctx.new_page()
|
|
page.goto("https://aistudio.google.com/generate-speech", wait_until="networkidle", timeout=60_000)
|
|
time.sleep(3)
|
|
|
|
# Open editor
|
|
page.locator('.text-input-container').first.click()
|
|
time.sleep(2)
|
|
|
|
# Step 1: Click Text mode (the other toggle)
|
|
page.locator('button[role="radio"]').filter(has_text="Text").click()
|
|
time.sleep(2)
|
|
page.screenshot(path="output/shot_3_text_mode.png", full_page=True)
|
|
dump_elements(page, "3_text_mode")
|
|
|
|
# Step 2: Back to Composer, click voice chip
|
|
page.locator('button[role="radio"]').filter(has_text="Composer").click()
|
|
time.sleep(2)
|
|
page.locator('button.voice-chip').first.click()
|
|
time.sleep(2)
|
|
page.screenshot(path="output/shot_4_voice_picker.png", full_page=True)
|
|
dump_elements(page, "4_voice_picker")
|
|
|
|
input("Press Enter to close...")
|
|
ctx.close()
|