mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-20 15:20:39 -07:00
feat(capture): dictation, personalities, 0.5.0
Ships the Capture release end to end. Global-hotkey dictation with synthetic paste into the focused app on macOS and Windows, an on-screen pill across recording / transcribing / refining, customizable push-to- talk and toggle chords, and an accessibility-permission prompt scoped to Settings → Captures with inline re-check feedback. Voice profiles gain optional personalities that power compose / rewrite / respond actions via a local Qwen3 LLM — shared with refinement, so there is one local LLM in the app, not two. Refinement hardened with deterministic Whisper-loop collapse before the LLM sees the transcript, per-capture flag snapshots for re-runs, and a ten-transcript evaluation harness across every bundled refinement size. Version bump 0.4.5 → 0.5.0. Co-Authored-By: Claude Opus 4.7 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
ed2eec591a
commit
87c582ad54
@@ -224,6 +224,73 @@ def _save_retry(
|
||||
return config.to_storage_path(audio_path)
|
||||
|
||||
|
||||
async def generate_audio_sync(
|
||||
*,
|
||||
profile_id: str,
|
||||
text: str,
|
||||
language: str,
|
||||
engine: str,
|
||||
model_size: str,
|
||||
seed: Optional[int] = None,
|
||||
instruct: Optional[str] = None,
|
||||
normalize: bool = True,
|
||||
max_chunk_chars: Optional[int] = None,
|
||||
crossfade_ms: Optional[int] = None,
|
||||
) -> bytes:
|
||||
"""Run a TTS generation synchronously and return the resulting wav bytes.
|
||||
|
||||
Unlike :func:`run_generation`, this path does not touch the
|
||||
``generations`` table, enqueue work, or write anything to the
|
||||
generations directory. It's used by ``POST /profiles/{id}/speak``
|
||||
when the caller passes ``persist=false`` — they just want the audio
|
||||
back in the HTTP response without polluting their history.
|
||||
|
||||
Loads the engine model on demand, runs ``generate_chunked``, optional
|
||||
normalize, then encodes in-memory via :func:`tts.audio_to_wav_bytes`
|
||||
(same helper ``/generate/stream`` uses).
|
||||
"""
|
||||
from ..backends import load_engine_model, get_tts_backend_for_engine, engine_needs_trim
|
||||
from ..utils.chunked_tts import generate_chunked
|
||||
from ..utils.audio import normalize_audio, trim_tts_output
|
||||
from . import tts
|
||||
|
||||
bg_db = next(get_db())
|
||||
try:
|
||||
tts_model = get_tts_backend_for_engine(engine)
|
||||
await load_engine_model(engine, model_size)
|
||||
|
||||
voice_prompt = await profiles.create_voice_prompt_for_profile(
|
||||
profile_id,
|
||||
bg_db,
|
||||
use_cache=True,
|
||||
engine=engine,
|
||||
)
|
||||
finally:
|
||||
bg_db.close()
|
||||
|
||||
trim_fn = trim_tts_output if engine_needs_trim(engine) else None
|
||||
|
||||
gen_kwargs: dict = dict(
|
||||
language=language,
|
||||
seed=seed,
|
||||
instruct=instruct,
|
||||
trim_fn=trim_fn,
|
||||
)
|
||||
if max_chunk_chars is not None:
|
||||
gen_kwargs["max_chunk_chars"] = max_chunk_chars
|
||||
if crossfade_ms is not None:
|
||||
gen_kwargs["crossfade_ms"] = crossfade_ms
|
||||
|
||||
audio, sample_rate = await generate_chunked(
|
||||
tts_model, text, voice_prompt, **gen_kwargs
|
||||
)
|
||||
|
||||
if normalize:
|
||||
audio = normalize_audio(audio)
|
||||
|
||||
return tts.audio_to_wav_bytes(audio, sample_rate)
|
||||
|
||||
|
||||
def _save_regenerate(
|
||||
*,
|
||||
generation_id: str,
|
||||
|
||||
Reference in New Issue
Block a user