Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 6 additions & 6 deletions src/phazor/phazor.c
Original file line number Diff line number Diff line change
Expand Up @@ -734,7 +734,7 @@ openmpt_module* mod = 0;

Music_Emu* emu;

// FFMPEG related -----------------------------------------------------
// FFmpeg related -----------------------------------------------------

FILE *ffm;
char exe_string[4096];
Expand All @@ -749,12 +749,12 @@ void start_ffmpeg(char uri[], int start_ms) {
int status = 0;
if (ff_start != NULL) status = ff_start(uri, start_ms, sample_rate_out);
else {
log_msg(LOG_ERROR, "pa: FFMPEG callback is NULL");
log_msg(LOG_ERROR, "pa: FFmpeg callback is NULL");
return;
}

if (status != 0) {
log_msg(LOG_ERROR, "pa: Error starting FFMPEG");
log_msg(LOG_ERROR, "pa: Error starting FFmpeg");
return;
}

Expand Down Expand Up @@ -2405,7 +2405,7 @@ int load_next() {

if (codec == UNKNOWN || config_always_ffmpeg == 1) {
codec = FFMPEG;
log_msg(LOG_INFO, "pa: Decode using FFMPEG\n");
log_msg(LOG_INFO, "pa: Decode using FFmpeg\n");
}

// Start decoders
Expand Down Expand Up @@ -2974,7 +2974,7 @@ void pump_decode() {
int b = 0;
if (ff_read != NULL) b = ff_read(ffm_buffer, 2048);
else {
log_msg(LOG_WARNING, "pa: FFMPEG read callback is NULL");
log_msg(LOG_WARNING, "pa: FFmpeg read callback is NULL");
decoder_eos();
return;
}
Expand All @@ -2989,7 +2989,7 @@ void pump_decode() {
read_to_buffer_char16(ffm_buffer, b);
pthread_mutex_unlock(&buffer_mutex);
if (b == 0) {
log_msg(LOG_INFO, "pa: FFMPEG has finished");
log_msg(LOG_INFO, "pa: FFmpeg has finished");
decoder_eos();
}
}
Expand Down
113 changes: 72 additions & 41 deletions src/tauon/t_modules/t_main.py
Original file line number Diff line number Diff line change
Expand Up @@ -15369,48 +15369,69 @@ def start_remote(self) -> None:
self.web_thread.start()
self.web_running = True

def download_ffmpeg(self, x) -> None:
def download_ffmpeg(self, _x) -> None:
"""Download FFmpeg portable binary to User Data dir, supports x64 Windows and Linux"""
def go() -> None:
url = "https://github.com/GyanD/codexffmpeg/releases/download/7.1.1/ffmpeg-7.1.1-essentials_build.zip"
sha = "04861d3339c5ebe38b56c19a15cf2c0cc97f5de4fa8910e4d47e5e6404e4a2d4"
if self.windows:
url = "https://github.com/GyanD/codexffmpeg/releases/download/8.0.1/ffmpeg-8.0.1-essentials_build.zip"
sha = "e2aaeaa0fdbc397d4794828086424d4aaa2102cef1fb6874f6ffd29c0b88b673"
elif not self.macos:
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"
sha = "e6f31af6c1ef49ceec8361185d70283ea8ad6c8ceed652d5dee66ec174a11eea"
self.show_message(_("Starting download..."))
try:
f = io.BytesIO()
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
dl = 0
total_bytes = int(r.headers.get("Content-Length", 0))
total_mb = round(total_bytes / 1000 / 1000) if total_bytes else 92

for data in r.iter_content(chunk_size=4096):
dl += len(data)
f.write(data)
mb = round(dl / 1000 / 1000)
if mb % 5 == 0:
self.show_message(_("Downloading... {MB}/{total_mb}").format(MB=mb, total_mb=total_mb))
except Exception as e:
logging.exception("Download failed")
self.show_message(_("Download failed"), str(e), mode="error")
return
with io.BytesIO() as f:
try:
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
dl = 0
total_bytes = int(r.headers.get("Content-Length", 0))
if self.windows:
fallback_mb = 101
elif not self.macos:
fallback_mb = 130
total_mb = round(total_bytes / 1000 / 1000) if total_bytes else fallback_mb

for data in r.iter_content(chunk_size=4096):
dl += len(data)
f.write(data)
mb = round(dl / 1000 / 1000)
if mb % 5 == 0:
self.show_message(_("Downloading... {MB}/{total_mb}").format(MB=mb, total_mb=total_mb))
except Exception as e:
logging.exception("Download failed")
self.show_message(_("Download failed"), str(e), mode="error")
return

f.seek(0)
checksum = hashlib.sha256(f.read()).hexdigest()
if checksum != sha:
self.show_message(_("Download completed but checksum failed"), mode="error")
logging.error(f"Checksum was {checksum} but expected {sha}")
return
self.show_message(_("Download completed.. extracting"))
f.seek(0)
z = zipfile.ZipFile(f, mode="r")
exe = z.open("ffmpeg-7.1.1-essentials_build/bin/ffmpeg.exe")
with (self.user_directory / "ffmpeg.exe").open("wb") as file:
file.write(exe.read())
f.seek(0)
checksum = hashlib.sha256(f.read()).hexdigest()
if checksum != sha:
self.show_message(_("Download completed but checksum failed"), mode="error")
logging.error(f"Checksum was {checksum} but expected {sha}")
return
self.show_message(_("Download completed.. extracting"))
f.seek(0)
if self.windows:
z = zipfile.ZipFile(f, mode="r")
with z.open("ffmpeg-8.0.1-essentials_build/bin/ffmpeg.exe") as exe, (self.user_directory / "ffmpeg.exe").open("wb") as file:
file.write(exe.read())

with z.open("ffmpeg-8.0.1-essentials_build/bin/ffprobe.exe") as exe, (self.user_directory / "ffprobe.exe").open("wb") as file:
file.write(exe.read())
elif not self.macos:
import tarfile

output_dir = self.user_directory
wanted = {"ffmpeg", "ffprobe"}

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

exe.close()
self.show_message(_("FFMPEG fetch complete"), mode="done")
if filename in wanted and member.isfile():
member.name = filename # strip directory structure
tar.extract(member, path=output_dir)


self.show_message(_("FFmpeg fetch complete"), mode="done")

shooter(go)

Expand Down Expand Up @@ -19068,13 +19089,13 @@ def get_tray_icon(self, name: str) -> str:
def test_ffmpeg(self) -> bool:
if self.get_ffmpeg():
return True
if self.windows:
self.show_message(_("This feature requires FFMPEG. Shall I can download that for you? (92MB)"), mode="confirm")
if not self.macos:
self.show_message(_("This feature requires FFmpeg. Shall I can download that for you? (100MB~)"), mode="confirm")
self.gui.message_box_confirm_callback = self.download_ffmpeg
self.gui.message_box_no_callback = None
self.gui.message_box_confirm_reference = (None,)
else:
self.show_message(_("FFMPEG could not be found"))
self.show_message(_("FFmpeg could not be found"))
return False

def get_ffmpeg(self) -> Path | None:
Expand All @@ -19087,6 +19108,11 @@ def get_ffmpeg(self) -> Path | None:
if path.is_file():
return path

# Portable Linux
path = self.user_directory / "ffmpeg"
if path.is_file():
return path

path = shutil.which("ffmpeg")
if path:
return Path(path)
Expand All @@ -19102,6 +19128,11 @@ def get_ffprobe(self) -> Path | None:
if path.is_file():
return path

# Portable Linux
path = self.user_directory / "ffprobe"
if path.is_file():
return path

path = shutil.which("ffprobe")
if path:
return Path(path)
Expand Down Expand Up @@ -40049,7 +40080,7 @@ def load_prefs(bag: Bag) -> None:
"Cache files from local sources too. (Useful for mounted network drives)")
prefs.always_ffmpeg = cf.sync_add(
"bool", "always-ffmpeg", prefs.always_ffmpeg,
"Prefer decoding using FFMPEG. Fixes stuttering on Raspberry Pi OS.")
"Prefer decoding using FFmpeg. Fixes stuttering on Raspberry Pi OS.")
prefs.volume_power = cf.sync_add(
"int", "volume-curve", prefs.volume_power,
"1=Linear volume control. Values above one give greater control bias over lower volume range. Default: 2")
Expand Down
9 changes: 5 additions & 4 deletions src/tauon/t_modules/t_phazor.py
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,7 @@ def worker(self) -> None:

class FFRun:
def __init__(self, tauon: Tauon) -> None:
self.tauon = tauon
self.tauon: Tauon = tauon
self.decoder = None

def close(self) -> None:
Expand All @@ -200,10 +200,11 @@ def close(self) -> None:

def start(self, uri: bytes, start_ms: int, samplerate: int) -> int:
self.close()
path = str(self.tauon.get_ffmpeg())
if not path:
ffmpeg_path = self.tauon.get_ffmpeg()
if ffmpeg_path is None:
self.tauon.test_ffmpeg()
return 1
path = str(ffmpeg_path)
cmd = [path]
cmd += ["-loglevel", "quiet"]
if start_ms > 0:
Expand All @@ -216,7 +217,7 @@ def start(self, uri: bytes, start_ms: int, samplerate: int) -> int:
try:
self.decoder = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, startupinfo=startupinfo)
except Exception:
logging.exception("Failed to start ffmpeg")
logging.exception("Failed to start FFmpeg")
return 1
return 0

Expand Down
2 changes: 1 addition & 1 deletion src/tauon/t_modules/t_stream.py
Original file line number Diff line number Diff line change
Expand Up @@ -245,7 +245,7 @@ def encode(self) -> None:
try:
ffmpeg_path = self.tauon.get_ffmpeg()
if ffmpeg_path is None:
logging.error("FFMPEG could not be found for stream encoder")
logging.error("FFmpeg could not be found for stream encoder")
self.encode_running = False
return

Expand Down
Loading