Same inaSpeechSegmenter engine split into reusable core/ (fftools, segmenter, exporters) + utils/ and a CustomTkinter multi-tab UI. Adds JSON and ffmpeg-script export. Extracted from the stream_tools app. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
100 lines
2.9 KiB
Python
100 lines
2.9 KiB
Python
"""
|
|
ffmpeg / ffprobe discovery and probing helpers.
|
|
Shared by both concat and singing detection features.
|
|
"""
|
|
|
|
import json
|
|
import os
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
# ── Suppress console windows on Windows ──────────────────────────────────────
|
|
_NO_WINDOW: dict = {}
|
|
if sys.platform == "win32":
|
|
_NO_WINDOW["creationflags"] = subprocess.CREATE_NO_WINDOW
|
|
|
|
|
|
def app_dir() -> str:
|
|
if getattr(sys, "frozen", False):
|
|
return os.path.dirname(sys.executable)
|
|
return os.path.dirname(os.path.abspath(__file__))
|
|
|
|
|
|
def find_tool(name: str) -> str | None:
|
|
"""Locate ffmpeg / ffprobe next to the exe / script, then on PATH."""
|
|
base = app_dir()
|
|
for sub in ["_internal", "..", ""]:
|
|
for variant in [name + ".exe", name]:
|
|
p = os.path.join(base, sub, variant)
|
|
if os.path.exists(p):
|
|
return os.path.abspath(p)
|
|
return shutil.which(name + ".exe") or shutil.which(name)
|
|
|
|
|
|
def probe_duration(path: str, ffprobe_bin: str | None = None) -> float:
|
|
"""Get video/audio duration in seconds via ffprobe."""
|
|
ffprobe = ffprobe_bin or find_tool("ffprobe")
|
|
if not ffprobe:
|
|
return 0.0
|
|
try:
|
|
r = subprocess.run(
|
|
[ffprobe, "-v", "error",
|
|
"-show_entries", "format=duration",
|
|
"-of", "json", path],
|
|
stdout=subprocess.PIPE, stderr=subprocess.PIPE,
|
|
text=True, timeout=20,
|
|
**_NO_WINDOW,
|
|
)
|
|
if r.returncode != 0:
|
|
return 0.0
|
|
data = json.loads(r.stdout or "{}")
|
|
return max(0.0, float(data.get("format", {}).get("duration", 0) or 0))
|
|
except Exception:
|
|
return 0.0
|
|
|
|
|
|
def extract_audio(video_path: str, output_wav: str,
|
|
ffmpeg_bin: str | None = None,
|
|
progress_cb=None) -> str:
|
|
"""
|
|
Extract audio from video file as 16kHz mono WAV.
|
|
Required format for inaSpeechSegmenter.
|
|
|
|
progress_cb: optional callable(message: str) for status updates.
|
|
"""
|
|
ffmpeg = ffmpeg_bin or find_tool("ffmpeg")
|
|
if not ffmpeg:
|
|
raise FileNotFoundError("ffmpeg not found. Install ffmpeg and add to PATH.")
|
|
|
|
if progress_cb:
|
|
progress_cb(f"Extracting audio from: {Path(video_path).name}")
|
|
|
|
cmd = [
|
|
ffmpeg,
|
|
"-i", video_path,
|
|
"-vn",
|
|
"-acodec", "pcm_s16le",
|
|
"-ar", "16000",
|
|
"-ac", "1",
|
|
"-y",
|
|
output_wav,
|
|
]
|
|
|
|
result = subprocess.run(
|
|
cmd,
|
|
capture_output=True,
|
|
text=True,
|
|
timeout=1800,
|
|
**_NO_WINDOW,
|
|
)
|
|
if result.returncode != 0:
|
|
raise RuntimeError(f"ffmpeg failed:\n{result.stderr[-500:]}")
|
|
|
|
if progress_cb:
|
|
size_mb = os.path.getsize(output_wav) / (1024 * 1024)
|
|
progress_cb(f"Audio extracted: {size_mb:.1f} MB")
|
|
|
|
return output_wav
|