fetchforge 2.0.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- fetchforge/__init__.py +3 -0
- fetchforge/__main__.py +4 -0
- fetchforge/cli.py +27 -0
- fetchforge/index.html +2978 -0
- fetchforge/provision.py +116 -0
- fetchforge/server.py +2198 -0
- fetchforge-2.0.0.dist-info/METADATA +131 -0
- fetchforge-2.0.0.dist-info/RECORD +12 -0
- fetchforge-2.0.0.dist-info/WHEEL +5 -0
- fetchforge-2.0.0.dist-info/entry_points.txt +2 -0
- fetchforge-2.0.0.dist-info/licenses/LICENSE +21 -0
- fetchforge-2.0.0.dist-info/top_level.txt +1 -0
fetchforge/provision.py
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"""Locate or provision an NVENC-capable ffmpeg.
|
|
2
|
+
|
|
3
|
+
FetchForge's H.265 encode uses hardware `hevc_nvenc`; a generic ffmpeg without
|
|
4
|
+
`--enable-nvenc` cannot run it, so presence alone is not enough — we verify the
|
|
5
|
+
encoder is actually listed. On Windows we can download a full build; on Linux the
|
|
6
|
+
build is driver/distro-coupled, so we guide the user instead.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
import os
|
|
11
|
+
import shutil
|
|
12
|
+
import subprocess
|
|
13
|
+
import sys
|
|
14
|
+
import zipfile
|
|
15
|
+
from pathlib import Path
|
|
16
|
+
from urllib.request import urlretrieve
|
|
17
|
+
|
|
18
|
+
IS_WINDOWS = sys.platform.startswith("win")
|
|
19
|
+
IS_MACOS = sys.platform == "darwin"
|
|
20
|
+
|
|
21
|
+
APPDATA_DIR = Path(os.getenv("LOCALAPPDATA", Path.home() / "AppData" / "Local")) / "FetchForge"
|
|
22
|
+
FFMPEG_CACHE = APPDATA_DIR / "ffmpeg"
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class ProvisionError(RuntimeError):
|
|
26
|
+
"""Raised when a working NVENC ffmpeg cannot be found or provisioned."""
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def ffmpeg_has_nvenc(ffmpeg: str) -> bool:
|
|
30
|
+
try:
|
|
31
|
+
out = subprocess.run(
|
|
32
|
+
[ffmpeg, "-hide_banner", "-encoders"],
|
|
33
|
+
capture_output=True, text=True, check=False,
|
|
34
|
+
)
|
|
35
|
+
except OSError:
|
|
36
|
+
return False
|
|
37
|
+
return "hevc_nvenc" in out.stdout
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def _cached_ffmpeg() -> str | None:
|
|
41
|
+
exe = FFMPEG_CACHE / ("ffmpeg.exe" if IS_WINDOWS else "ffmpeg")
|
|
42
|
+
return str(exe) if exe.exists() else None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def find_nvenc_ffmpeg() -> str | None:
|
|
46
|
+
candidates = [_cached_ffmpeg(), shutil.which("ffmpeg")]
|
|
47
|
+
for cand in candidates:
|
|
48
|
+
if cand and ffmpeg_has_nvenc(cand):
|
|
49
|
+
return cand
|
|
50
|
+
return None
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def provision_windows(download=urlretrieve) -> str:
|
|
54
|
+
"""Download + unpack a full ffmpeg build into FFMPEG_CACHE, return ffmpeg.exe."""
|
|
55
|
+
FFMPEG_CACHE.mkdir(parents=True, exist_ok=True)
|
|
56
|
+
archive = FFMPEG_CACHE / "ffmpeg-release-full.zip"
|
|
57
|
+
try:
|
|
58
|
+
download(_windows_zip_url(), archive)
|
|
59
|
+
with zipfile.ZipFile(archive) as zf:
|
|
60
|
+
for member in zf.namelist():
|
|
61
|
+
name = Path(member).name
|
|
62
|
+
if name in ("ffmpeg.exe", "ffprobe.exe"):
|
|
63
|
+
with zf.open(member) as src, open(FFMPEG_CACHE / name, "wb") as dst:
|
|
64
|
+
shutil.copyfileobj(src, dst)
|
|
65
|
+
except (OSError, zipfile.BadZipFile) as e:
|
|
66
|
+
raise ProvisionError(
|
|
67
|
+
f"Failed to download or unpack ffmpeg for Windows: {e}\n"
|
|
68
|
+
"Install ffmpeg manually (put ffmpeg.exe/ffprobe.exe on PATH) and re-run."
|
|
69
|
+
) from e
|
|
70
|
+
finally:
|
|
71
|
+
archive.unlink(missing_ok=True)
|
|
72
|
+
exe = FFMPEG_CACHE / "ffmpeg.exe"
|
|
73
|
+
if not exe.exists():
|
|
74
|
+
raise ProvisionError("Downloaded ffmpeg archive did not contain ffmpeg.exe")
|
|
75
|
+
return str(exe)
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
def _windows_zip_url() -> str:
|
|
79
|
+
# BtbN's FFmpeg-Builds GPL "latest" build (github.com/BtbN/FFmpeg-Builds):
|
|
80
|
+
# ships as a .zip (no extra deps to unpack) and includes hevc_nvenc.
|
|
81
|
+
return ("https://github.com/BtbN/FFmpeg-Builds/releases/latest/download/"
|
|
82
|
+
"ffmpeg-master-latest-win64-gpl.zip")
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def linux_guidance() -> str:
|
|
86
|
+
return (
|
|
87
|
+
"FetchForge needs an NVENC-capable ffmpeg and the NVIDIA proprietary driver.\n"
|
|
88
|
+
"Install ffmpeg from your package manager, e.g.:\n"
|
|
89
|
+
" Fedora/Nobara: sudo dnf install ffmpeg\n"
|
|
90
|
+
" Debian/Ubuntu: sudo apt install ffmpeg\n"
|
|
91
|
+
"Then re-run `fetchforge`."
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
|
|
95
|
+
def macos_guidance() -> str:
|
|
96
|
+
return (
|
|
97
|
+
"FetchForge's H.265 transcode requires an NVIDIA GPU with NVENC, which "
|
|
98
|
+
"macOS does not provide. The download + audio-extract features still work, "
|
|
99
|
+
"but the video transcode cannot run on this platform."
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def _use(ffmpeg: str) -> str:
|
|
104
|
+
os.environ["PATH"] = str(Path(ffmpeg).parent) + os.pathsep + os.environ.get("PATH", "")
|
|
105
|
+
return ffmpeg
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def ensure_ffmpeg() -> str:
|
|
109
|
+
existing = find_nvenc_ffmpeg()
|
|
110
|
+
if existing:
|
|
111
|
+
return _use(existing)
|
|
112
|
+
if IS_WINDOWS:
|
|
113
|
+
return _use(provision_windows())
|
|
114
|
+
if IS_MACOS:
|
|
115
|
+
raise ProvisionError(macos_guidance())
|
|
116
|
+
raise ProvisionError(linux_guidance())
|