Add campaign layer, GPU model caching, file browser cleanup
- Campaigns: new table, CRUD API, React context + provider
- Sessions: scoped to campaigns, paths under campaigns/{id}/
- File browser: scoped per campaign, removed copy/paste/autoPlay
- Sidebar: campaign selector dropdown at top
- Transcribe: GPU model cached/released via job counter
- Jobs: status text updates dynamically in real-time
- Auto-redirect: blocked when summarize job is active
This commit is contained in:
@@ -13,6 +13,53 @@ from ..logging_config import get_logger
|
||||
|
||||
log = get_logger(__name__)
|
||||
|
||||
# Module-level model cache — kept alive across jobs so back-to-back
|
||||
# transcriptions don't reload from disk each time.
|
||||
_whisper_model = None
|
||||
_whisper_config = {} # {"model_size": str, "compute_type": str, "device": str}
|
||||
|
||||
|
||||
def _get_model(model_size: str, compute_type: str, device: str):
|
||||
global _whisper_model, _whisper_config
|
||||
if (
|
||||
_whisper_model is not None
|
||||
and _whisper_config.get("model_size") == model_size
|
||||
and _whisper_config.get("compute_type") == compute_type
|
||||
and _whisper_config.get("device") == device
|
||||
):
|
||||
log.info("Reusing cached Whisper model (%s, %s, %s)", model_size, compute_type, device)
|
||||
return _whisper_model
|
||||
log.info("Loading Whisper model (%s, %s, %s)", model_size, compute_type, device)
|
||||
_unload_cached()
|
||||
_whisper_model = whisperx.load_model(model_size, device, compute_type=compute_type)
|
||||
_whisper_config = {"model_size": model_size, "compute_type": compute_type, "device": device}
|
||||
return _whisper_model
|
||||
|
||||
|
||||
def _unload_cached():
|
||||
global _whisper_model, _whisper_config
|
||||
if _whisper_model is not None:
|
||||
log.info("Unloading cached Whisper model")
|
||||
del _whisper_model
|
||||
_whisper_model = None
|
||||
_whisper_config = {}
|
||||
gc.collect()
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.synchronize()
|
||||
torch.cuda.empty_cache()
|
||||
torch.cuda.reset_peak_memory_stats()
|
||||
|
||||
|
||||
def unload_models():
|
||||
"""Release all GPU memory held by transcription models.
|
||||
Called by the job runner when no more GPU jobs are queued."""
|
||||
_unload_cached()
|
||||
gc.collect()
|
||||
if torch.cuda.is_available():
|
||||
torch.cuda.synchronize()
|
||||
torch.cuda.empty_cache()
|
||||
torch.cuda.reset_peak_memory_stats()
|
||||
|
||||
|
||||
def transcribe_and_diarize(
|
||||
audio_path: Path,
|
||||
@@ -33,7 +80,7 @@ def transcribe_and_diarize(
|
||||
|
||||
report("Loading transcription model...")
|
||||
try:
|
||||
model = whisperx.load_model(model_size, device, compute_type=compute_type)
|
||||
model = _get_model(model_size, compute_type, device)
|
||||
except RuntimeError as e:
|
||||
if "out of memory" in str(e).lower():
|
||||
raise TranscriptionError(
|
||||
@@ -59,22 +106,21 @@ def transcribe_and_diarize(
|
||||
raise TranscriptionError(f"Transcription failed: {e}", cause=e)
|
||||
except Exception as e:
|
||||
raise TranscriptionError(f"Transcription failed: {e}", cause=e)
|
||||
finally:
|
||||
del model
|
||||
gc.collect()
|
||||
if device == "cuda":
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
try:
|
||||
report("Aligning...")
|
||||
align_model, metadata = whisperx.load_align_model(language_code=result["language"], device=device)
|
||||
result = whisperx.align(result["segments"], align_model, metadata, audio, device, return_char_alignments=False)
|
||||
del align_model
|
||||
except Exception as e:
|
||||
raise TranscriptionError(f"Alignment step failed: {e}", cause=e)
|
||||
finally:
|
||||
try:
|
||||
del align_model
|
||||
except NameError:
|
||||
pass
|
||||
gc.collect()
|
||||
if device == "cuda":
|
||||
torch.cuda.empty_cache()
|
||||
except Exception as e:
|
||||
raise TranscriptionError(f"Alignment step failed: {e}", cause=e)
|
||||
|
||||
if hf_token:
|
||||
try:
|
||||
@@ -91,6 +137,14 @@ def transcribe_and_diarize(
|
||||
f"your HF token belongs to.\n{msg}", cause=e
|
||||
)
|
||||
raise DiarizationError(f"Diarization failed: {msg}", cause=e)
|
||||
finally:
|
||||
try:
|
||||
del diarize_model
|
||||
except NameError:
|
||||
pass
|
||||
gc.collect()
|
||||
if device == "cuda":
|
||||
torch.cuda.empty_cache()
|
||||
else:
|
||||
report("No HF token set - skipping speaker diarization (all speech will be unattributed)")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user