54 lines
1.7 KiB
Python
54 lines
1.7 KiB
Python
# Copyright 2026 Umbra Tools (umbratools.com)
|
|
# SPDX-License-Identifier: Apache-2.0
|
|
|
|
from __future__ import annotations
|
|
|
|
import logging
|
|
from pathlib import Path
|
|
from typing import Any
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
class ConsistencyChecker:
|
|
def __init__(self) -> None:
|
|
self._embedding_manager = None
|
|
|
|
def _get_embeddings(self):
|
|
if self._embedding_manager is None:
|
|
from umbraaudio.voices.embeddings import EmbeddingManager
|
|
|
|
self._embedding_manager = EmbeddingManager()
|
|
return self._embedding_manager
|
|
|
|
def check_cross_chapter(
|
|
self,
|
|
chapter_audio_files: list[Path],
|
|
character_name: str,
|
|
threshold: float = 0.85,
|
|
) -> dict[str, Any]:
|
|
if len(chapter_audio_files) < 2:
|
|
return {"consistent": True, "scores": [], "message": "Not enough files"}
|
|
|
|
try:
|
|
manager = self._get_embeddings()
|
|
embeddings = [manager.extract(f) for f in chapter_audio_files]
|
|
|
|
scores = []
|
|
reference = embeddings[0]
|
|
for i, emb in enumerate(embeddings[1:], 1):
|
|
score = manager.compare(reference, emb)
|
|
scores.append({"chapter": i, "score": score})
|
|
|
|
min_score = min(s["score"] for s in scores) if scores else 1.0
|
|
return {
|
|
"consistent": min_score >= threshold,
|
|
"min_score": min_score,
|
|
"threshold": threshold,
|
|
"scores": scores,
|
|
"character": character_name,
|
|
}
|
|
except Exception as e:
|
|
logger.error("Consistency check failed for %s: %s", character_name, e)
|
|
return {"consistent": False, "error": str(e)}
|