Backend:
- Split monolithic main.py into backend/{api/routers,core,schemas,services}
- core/db.py: allowlist-gated migrations, db_conn context manager (kills SQL injection on ALTER)
- core/tasks.py: lock-guarded listener add/remove/push, snapshot-before-iterate
- services/ffmpeg_utils.py: run_ffmpeg helper with concurrency semaphore, EAGAIN retry, guaranteed reap
- services/segmentation.py: Bengali/CJK/Arabic punctuation, ultra-short tier, stitch_adjacent_shorts,
bounded-loop merge; public clean_up_segments API
- services/model_manager.py: robust lock.locked() handling
- api/routers/dub_core.py: job_id traversal guard, thread-safe _active_procs, timeouts on ffmpeg/demucs,
POST /dub/cleanup-segments endpoint
- api/routers/dub_export.py: guarded SSE listener remove, ffmpeg timeouts via run_ffmpeg
- api/routers/exports.py: destination_path validation, safe source resolver, subprocess list-form
- api/routers/generation.py: contextlib.suppress on tempfile cleanup, db_conn usage, safe output-path helper
- api/routers/system.py: try/finally tmp cleanup, subprocess timeouts
- schemas/requests.py: TranslateSegment.id int->str to match hex segment IDs
- main.py: threading.Lock around crash log writes
Frontend:
- components/SearchableSelect.jsx: popover combobox with search, keyboard nav, popular+recent pins, 200-item cap
- App.jsx: wire SearchableSelect for dub language / ISO code / voice-gen language; Clean Up segments button;
fix blob URL leak (object-shaped prev in setter, unmount cleanup via ref)
- components/WaveformTimeline.jsx: explicit <video> detach instead of innerHTML='' to release decoder
- index.css: ss-* combobox styles matching Gruvbox theme
Tests:
- tests/test_segmentation.py (26 cases), test_dub_transcribe.py, test_dub_export_unique.py, conftest.py
Chore:
- .gitignore: exclude omnivoice.zip, /research/ reference clones
- Remove tracked stray root test scripts + crash_log.txt
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
52 lines
1.9 KiB
Python
52 lines
1.9 KiB
Python
import sys
|
|
import os
|
|
os.environ["PATH"] += os.pathsep + "/opt/homebrew/bin:/usr/local/bin"
|
|
|
|
try:
|
|
from imageio_ffmpeg import get_ffmpeg_exe
|
|
ffmpeg_path = get_ffmpeg_exe()
|
|
os.environ["PATH"] = os.path.dirname(ffmpeg_path) + os.pathsep + os.environ.get("PATH", "")
|
|
except Exception as e:
|
|
pass
|
|
|
|
import mlx_whisper
|
|
import time
|
|
import subprocess
|
|
|
|
audio_file = "/Users/user4/Desktop/voice-design/OmniVoice/data/preview/1c43531cb0ae.mov"
|
|
|
|
print("Starting transcription...", flush=True)
|
|
start = time.time()
|
|
result = mlx_whisper.transcribe(audio_file, path_or_hf_repo="mlx-community/whisper-large-v3-mlx")
|
|
transcript = result.get("text", "").strip()
|
|
trans_time = time.time() - start
|
|
print(f"Transcript ({trans_time:.2f}s):\n{transcript}\n")
|
|
|
|
# Baseline Google NMT
|
|
try:
|
|
from deep_translator import GoogleTranslator
|
|
print("Translating with Google NMT...", flush=True)
|
|
start = time.time()
|
|
google_tgt = GoogleTranslator(source="auto", target="bn")
|
|
google_text = google_tgt.translate(transcript)
|
|
google_time = time.time() - start
|
|
print(f"Google Bengali ({google_time:.2f}s):\n{google_text}\n")
|
|
except Exception as e:
|
|
print(f"Google Failed: {e}\n")
|
|
|
|
# APFEL
|
|
print("Translating with Apfel...", flush=True)
|
|
start = time.time()
|
|
try:
|
|
prompt = f"You are a professional dubbing translator. Translate the following text into Bengali. Output ONLY the translated text.\n{transcript}"
|
|
# Use login shell to ensure apfel alias/function is loaded
|
|
prompt_esc = prompt.replace('"', '\\"')
|
|
apfel_res = subprocess.run(["zsh", "-lc", f'apfel "{prompt_esc}"'], capture_output=True, text=True)
|
|
apfel_time = time.time() - start
|
|
if apfel_res.returncode == 0:
|
|
print(f"Apfel Bengali ({apfel_time:.2f}s):\n{apfel_res.stdout.strip()}\n")
|
|
else:
|
|
print(f"Apfel CLI failed: {apfel_res.stderr}\n")
|
|
except Exception as e:
|
|
print(f"Apfel Failed: {e}\n")
|