Skip to content

Commit 48a589f

Browse files
committed
fix: Add support for downloading x64 Linux ffmpeg, fixes portable build
1 parent 585f09c commit 48a589f

File tree

1 file changed

+72
-41
lines changed

1 file changed

+72
-41
lines changed

src/tauon/t_modules/t_main.py

Lines changed: 72 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -15369,48 +15369,69 @@ def start_remote(self) -> None:
1536915369
self.web_thread.start()
1537015370
self.web_running = True
1537115371

15372-
def download_ffmpeg(self, x) -> None:
15372+
def download_ffmpeg(self, _x) -> None:
15373+
"""Download FFmpeg portable binary to User Data dir, supports x64 Windows and Linux"""
1537315374
def go() -> None:
15374-
url = "https://github.com/GyanD/codexffmpeg/releases/download/7.1.1/ffmpeg-7.1.1-essentials_build.zip"
15375-
sha = "04861d3339c5ebe38b56c19a15cf2c0cc97f5de4fa8910e4d47e5e6404e4a2d4"
15375+
if self.windows:
15376+
url = "https://github.com/GyanD/codexffmpeg/releases/download/8.0.1/ffmpeg-8.0.1-essentials_build.zip"
15377+
sha = "e2aaeaa0fdbc397d4794828086424d4aaa2102cef1fb6874f6ffd29c0b88b673"
15378+
elif not self.macos:
15379+
url = "https://github.com/BtbN/FFmpeg-Builds/releases/download/autobuild-2026-03-02-13-06/ffmpeg-n8.0.1-66-g27b8d1a017-linux64-gpl-8.0.tar.xz"
15380+
sha = "e6f31af6c1ef49ceec8361185d70283ea8ad6c8ceed652d5dee66ec174a11eea"
1537615381
self.show_message(_("Starting download..."))
15377-
try:
15378-
f = io.BytesIO()
15379-
with requests.get(url, stream=True, timeout=1800) as r: # ffmpeg is 92MB, give it half an hour in case someone is willing to suffer it on a slow connection
15380-
dl = 0
15381-
total_bytes = int(r.headers.get("Content-Length", 0))
15382-
total_mb = round(total_bytes / 1000 / 1000) if total_bytes else 92
15383-
15384-
for data in r.iter_content(chunk_size=4096):
15385-
dl += len(data)
15386-
f.write(data)
15387-
mb = round(dl / 1000 / 1000)
15388-
if mb % 5 == 0:
15389-
self.show_message(_("Downloading... {MB}/{total_mb}").format(MB=mb, total_mb=total_mb))
15390-
except Exception as e:
15391-
logging.exception("Download failed")
15392-
self.show_message(_("Download failed"), str(e), mode="error")
15393-
return
15382+
with io.BytesIO() as f:
15383+
try:
15384+
with requests.get(url, stream=True, timeout=1800) as r: # Windows ffmpeg is 101MB, Linux 130MB, give it half an hour in case someone is willing to suffer it on a slow connection
15385+
dl = 0
15386+
total_bytes = int(r.headers.get("Content-Length", 0))
15387+
if self.windows:
15388+
fallback_mb = 101
15389+
elif not self.macos:
15390+
fallback_mb = 130
15391+
total_mb = round(total_bytes / 1000 / 1000) if total_bytes else fallback_mb
15392+
15393+
for data in r.iter_content(chunk_size=4096):
15394+
dl += len(data)
15395+
f.write(data)
15396+
mb = round(dl / 1000 / 1000)
15397+
if mb % 5 == 0:
15398+
self.show_message(_("Downloading... {MB}/{total_mb}").format(MB=mb, total_mb=total_mb))
15399+
except Exception as e:
15400+
logging.exception("Download failed")
15401+
self.show_message(_("Download failed"), str(e), mode="error")
15402+
return
1539415403

15395-
f.seek(0)
15396-
checksum = hashlib.sha256(f.read()).hexdigest()
15397-
if checksum != sha:
15398-
self.show_message(_("Download completed but checksum failed"), mode="error")
15399-
logging.error(f"Checksum was {checksum} but expected {sha}")
15400-
return
15401-
self.show_message(_("Download completed.. extracting"))
15402-
f.seek(0)
15403-
z = zipfile.ZipFile(f, mode="r")
15404-
exe = z.open("ffmpeg-7.1.1-essentials_build/bin/ffmpeg.exe")
15405-
with (self.user_directory / "ffmpeg.exe").open("wb") as file:
15406-
file.write(exe.read())
15404+
f.seek(0)
15405+
checksum = hashlib.sha256(f.read()).hexdigest()
15406+
if checksum != sha:
15407+
self.show_message(_("Download completed but checksum failed"), mode="error")
15408+
logging.error(f"Checksum was {checksum} but expected {sha}")
15409+
return
15410+
self.show_message(_("Download completed.. extracting"))
15411+
f.seek(0)
15412+
if self.windows:
15413+
z = zipfile.ZipFile(f, mode="r")
15414+
with z.open("ffmpeg-8.0.1-essentials_build/bin/ffmpeg.exe") as exe, (self.user_directory / "ffmpeg.exe").open("wb") as file:
15415+
file.write(exe.read())
15416+
15417+
with z.open("ffmpeg-8.0.1-essentials_build/bin/ffprobe.exe") as exe, (self.user_directory / "ffprobe.exe").open("wb") as file:
15418+
file.write(exe.read())
15419+
elif not self.macos:
15420+
import tarfile
15421+
15422+
output_dir = self.user_directory
15423+
wanted = {"ffmpeg", "ffprobe"}
1540715424

15408-
exe = z.open("ffmpeg-7.1.1-essentials_build/bin/ffprobe.exe")
15409-
with (self.user_directory / "ffprobe.exe").open("wb") as file:
15410-
file.write(exe.read())
15425+
with tarfile.open(fileobj=f, mode="r:xz") as tar:
15426+
for member in tar.getmembers():
15427+
filename = Path(member.name).name
1541115428

15412-
exe.close()
15413-
self.show_message(_("FFMPEG fetch complete"), mode="done")
15429+
if filename in wanted and member.isfile():
15430+
member.name = filename # strip directory structure
15431+
tar.extract(member, path=output_dir)
15432+
15433+
15434+
self.show_message(_("FFmpeg fetch complete"), mode="done")
1541415435

1541515436
shooter(go)
1541615437

@@ -19068,13 +19089,13 @@ def get_tray_icon(self, name: str) -> str:
1906819089
def test_ffmpeg(self) -> bool:
1906919090
if self.get_ffmpeg():
1907019091
return True
19071-
if self.windows:
19072-
self.show_message(_("This feature requires FFMPEG. Shall I can download that for you? (92MB)"), mode="confirm")
19092+
if not self.macos:
19093+
self.show_message(_("This feature requires FFmpeg. Shall I can download that for you? (100MB~)"), mode="confirm")
1907319094
self.gui.message_box_confirm_callback = self.download_ffmpeg
1907419095
self.gui.message_box_no_callback = None
1907519096
self.gui.message_box_confirm_reference = (None,)
1907619097
else:
19077-
self.show_message(_("FFMPEG could not be found"))
19098+
self.show_message(_("FFmpeg could not be found"))
1907819099
return False
1907919100

1908019101
def get_ffmpeg(self) -> Path | None:
@@ -19087,6 +19108,11 @@ def get_ffmpeg(self) -> Path | None:
1908719108
if path.is_file():
1908819109
return path
1908919110

19111+
# Portable Linux
19112+
path = self.user_directory / "ffmpeg"
19113+
if path.is_file():
19114+
return path
19115+
1909019116
path = shutil.which("ffmpeg")
1909119117
if path:
1909219118
return Path(path)
@@ -19102,6 +19128,11 @@ def get_ffprobe(self) -> Path | None:
1910219128
if path.is_file():
1910319129
return path
1910419130

19131+
# Portable Linux
19132+
path = self.user_directory / "ffprobe"
19133+
if path.is_file():
19134+
return path
19135+
1910519136
path = shutil.which("ffprobe")
1910619137
if path:
1910719138
return Path(path)
@@ -40049,7 +40080,7 @@ def load_prefs(bag: Bag) -> None:
4004940080
"Cache files from local sources too. (Useful for mounted network drives)")
4005040081
prefs.always_ffmpeg = cf.sync_add(
4005140082
"bool", "always-ffmpeg", prefs.always_ffmpeg,
40052-
"Prefer decoding using FFMPEG. Fixes stuttering on Raspberry Pi OS.")
40083+
"Prefer decoding using FFmpeg. Fixes stuttering on Raspberry Pi OS.")
4005340084
prefs.volume_power = cf.sync_add(
4005440085
"int", "volume-curve", prefs.volume_power,
4005540086
"1=Linear volume control. Values above one give greater control bias over lower volume range. Default: 2")

0 commit comments

Comments
 (0)