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