File size: 4,346 Bytes
a9536c4 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 | from __future__ import annotations
import os
import shutil
import subprocess
import sys
from pathlib import Path
from typing import MutableMapping, Optional
ROOT_DIR = Path(__file__).resolve().parent.parent
def get_runtime_root(root_dir: Path | str | None = None) -> Path:
if root_dir is not None:
return Path(root_dir).expanduser()
if getattr(sys, "frozen", False):
return Path(sys.executable).resolve().parent
return ROOT_DIR
def _normalize_path_key(path: Path | str) -> str:
return os.path.normcase(os.path.normpath(str(path)))
def _iter_ffmpeg_bin_candidates(root_dir: Path) -> list[Path]:
candidates = [
root_dir / "tools" / "ffmpeg" / "bin",
root_dir / "tools" / "ffmpeg",
]
meipass = getattr(sys, "_MEIPASS", None)
if meipass:
meipass_root = Path(meipass)
candidates.extend(
[
meipass_root / "tools" / "ffmpeg" / "bin",
meipass_root / "tools" / "ffmpeg",
]
)
try:
import imageio_ffmpeg
candidates.append(Path(imageio_ffmpeg.get_ffmpeg_exe()).resolve().parent)
except Exception:
pass
unique: list[Path] = []
seen: set[str] = set()
for candidate in candidates:
key = _normalize_path_key(candidate)
if key not in seen:
unique.append(candidate)
seen.add(key)
return unique
def get_ffmpeg_bin_dir(root_dir: Path | str | None = None) -> Optional[Path]:
runtime_root = get_runtime_root(root_dir)
ffmpeg_name = "ffmpeg.exe" if os.name == "nt" else "ffmpeg"
for candidate in _iter_ffmpeg_bin_candidates(runtime_root):
if (candidate / ffmpeg_name).exists():
return candidate
found = shutil.which("ffmpeg")
if found:
return Path(found).resolve().parent
return None
def _check_executable_runs(executable: Path, label: str) -> None:
try:
result = subprocess.run(
[str(executable), "-version"],
capture_output=True,
text=True,
timeout=10,
)
except Exception as exc:
raise RuntimeError(f"{label} 无法启动: {executable} ({exc})") from exc
if result.returncode != 0:
details = "\n".join(
part.strip()
for part in (result.stdout, result.stderr)
if part and part.strip()
)
if not details:
details = f"退出码: {result.returncode}"
raise RuntimeError(f"{label} 无法正常运行: {executable}\n{details}")
def configure_ffmpeg_runtime(
root_dir: Path | str | None = None,
env: MutableMapping[str, str] | None = None,
) -> Optional[Path]:
env = os.environ if env is None else env
bin_dir = get_ffmpeg_bin_dir(root_dir=root_dir)
if bin_dir is None:
return None
bin_dir_str = str(bin_dir)
current_path = env.get("PATH", "")
path_entries = [entry for entry in current_path.split(os.pathsep) if entry]
normalized_entries = {_normalize_path_key(entry) for entry in path_entries}
if _normalize_path_key(bin_dir_str) not in normalized_entries:
env["PATH"] = bin_dir_str if not current_path else bin_dir_str + os.pathsep + current_path
ffmpeg_name = "ffmpeg.exe" if os.name == "nt" else "ffmpeg"
ffmpeg_path = bin_dir / ffmpeg_name
if ffmpeg_path.exists():
_check_executable_runs(ffmpeg_path, "ffmpeg")
env.setdefault("FFMPEG_BINARY", str(ffmpeg_path))
env.setdefault("IMAGEIO_FFMPEG_EXE", str(ffmpeg_path))
ffprobe_name = "ffprobe.exe" if os.name == "nt" else "ffprobe"
ffprobe_path = bin_dir / ffprobe_name
if ffprobe_path.exists():
_check_executable_runs(ffprobe_path, "ffprobe")
env.setdefault("FFPROBE_BINARY", str(ffprobe_path))
else:
resolved_ffprobe = shutil.which("ffprobe", path=env.get("PATH", ""))
if resolved_ffprobe is None:
raise RuntimeError("未找到 ffprobe。请安装 ffmpeg/ffprobe,或将 ffprobe 放入 tools/ffmpeg/bin。")
_check_executable_runs(Path(resolved_ffprobe), "ffprobe")
env.setdefault("FFPROBE_BINARY", str(Path(resolved_ffprobe)))
return bin_dir
|