feat: add multimodal search and granular performance diagnostics
Core CI / core (ubuntu-latest) (push) Failing after 3m31s
Core CI / core (macos-latest) (push) Canceled after 0s
Core CI / core (windows-latest) (push) Canceled after 0s
Core CI / desktop (ubuntu-latest) (push) Successful in 5m39s
Core CI / desktop (macos-latest) (push) Canceled after 0s
Core CI / desktop (windows-latest) (push) Canceled after 0s
Core CI / core (ubuntu-latest) (push) Failing after 3m31s
Core CI / core (macos-latest) (push) Canceled after 0s
Core CI / core (windows-latest) (push) Canceled after 0s
Core CI / desktop (ubuntu-latest) (push) Successful in 5m39s
Core CI / desktop (macos-latest) (push) Canceled after 0s
Core CI / desktop (windows-latest) (push) Canceled after 0s
This commit is contained in:
1 parent
d3bb8c7618
commit
b8400c4fbf
110 files changed
+79395
-250
No files matched your search
@@ -0,0 +1,32 @@
|
||||
# Standalone media decoder tools
|
||||
|
||||
The desktop distribution must include FFmpeg and FFprobe alongside its Google
|
||||
worker. End users should not install tools or configure paths. Development and
|
||||
release preparation use:
|
||||
|
||||
```sh
|
||||
python tools/media-runtime/provision.py --stage
|
||||
python -m unittest discover -s tools/media-runtime -p 'test_*.py'
|
||||
```
|
||||
|
||||
The manifest pins a dated BtbN release, exact archive size, and published SHA-256.
|
||||
Provisioning checks the cache or downloads/verifies the archive, extracts only the
|
||||
two standalone decoder executables and notices, and stages them under the native
|
||||
resources directory. Executables and archives are ignored by Git. No global
|
||||
installation occurs. Movie/audio decoding runs in bounded subprocesses.
|
||||
|
||||
Windows x86-64/ARM64 and Linux x86-64/ARM64 have pinned artifacts. Only Windows
|
||||
x86-64 has been executed here. A vetted macOS decoder build/staging path remains
|
||||
a release gate; generic media code can accept native tools through explicit paths.
|
||||
The selected build is FFmpeg 9.0.2 with LGPL configuration. Keep original notices
|
||||
and corresponding source/build provenance with distribution; installer licensing
|
||||
and all-platform execution are still open production gates.
|
||||
|
||||
Source/build provenance:
|
||||
- https://github.com/BtbN/FFmpeg-Builds/releases/tag/autobuild-2026-10-12-13-12
|
||||
- https://github.com/BtbN/FFmpeg-Builds
|
||||
- https://ffmpeg.org/legal.html
|
||||
|
||||
FFmpeg capabilities vary by build. An unsupported or corrupt file must report an
|
||||
error while remaining filename-searchable; it must never freeze search or be
|
||||
silently reported as fully indexed.
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"provider": "BtbN/FFmpeg-Builds",
|
||||
"release": "autobuild-2026-10-12-13-12",
|
||||
"version": "9.0.2-27-g7e93014c5b",
|
||||
"platforms": {
|
||||
"windows-x86_64": {
|
||||
"archive": "ffmpeg-n9.0.2-27-g7e93014c5b-win64-lgpl-9.0.zip",
|
||||
"bytes": 171476229,
|
||||
"sha256": "104a019fe34c0747b208bde4842a0bac8b70a307cc2bb33aa4648fc15ddb2e39"
|
||||
},
|
||||
"windows-aarch64": {
|
||||
"archive": "ffmpeg-n9.0.2-27-g7e93014c5b-winarm64-lgpl-9.0.zip",
|
||||
"bytes": 118006941,
|
||||
"sha256": "68d3bac585b80b6f17e459eb4a203ed1f01b7635d4c611f1685de1ae65ca0987"
|
||||
},
|
||||
"linux-x86_64": {
|
||||
"archive": "ffmpeg-n9.0.2-27-g7e93014c5b-linux64-lgpl-9.0.tar.xz",
|
||||
"bytes": 137901812,
|
||||
"sha256": "12445b38a4700ba1ae7c9c7e57062f602c927dc4187555bfadc9f3e2f75f05b0"
|
||||
},
|
||||
"linux-aarch64": {
|
||||
"archive": "ffmpeg-n9.0.2-27-g7e93014c5b-linuxarm64-lgpl-9.0.tar.xz",
|
||||
"bytes": 117008524,
|
||||
"sha256": "c9160ae8440c17c016fb2f6b3fcce4bb9d633a15845e908b2303948a7724f227"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Verify and stage standalone FFmpeg tools. No user-wide installation or GPU use."""
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import os
|
||||
from pathlib import Path
|
||||
import platform
|
||||
import shutil
|
||||
import tarfile
|
||||
import tempfile
|
||||
import urllib.request
|
||||
import zipfile
|
||||
|
||||
ROOT = Path(__file__).resolve().parents[2]
|
||||
MAX_MEMBER = 256 * 1024 * 1024
|
||||
|
||||
|
||||
def verified(path, artifact):
|
||||
if not path.is_file() or path.stat().st_size != artifact["bytes"]:
|
||||
return False
|
||||
with path.open("rb") as stream:
|
||||
return hashlib.file_digest(stream, "sha256").hexdigest() == artifact["sha256"]
|
||||
|
||||
|
||||
def install(stream, target, expected):
|
||||
if not 0 < expected <= MAX_MEMBER:
|
||||
raise ValueError("Archive member exceeds size ceiling")
|
||||
target.parent.mkdir(parents=True, exist_ok=True)
|
||||
temporary = target.with_suffix(target.suffix + ".partial")
|
||||
try:
|
||||
with temporary.open("wb") as output:
|
||||
copied = 0
|
||||
while block := stream.read(65536):
|
||||
copied += len(block)
|
||||
if copied > expected:
|
||||
raise ValueError("Archive member exceeds declared length")
|
||||
output.write(block)
|
||||
if copied != expected:
|
||||
raise ValueError("Archive member is truncated")
|
||||
if target.name in {"ffmpeg", "ffprobe"}:
|
||||
temporary.chmod(0o755)
|
||||
os.replace(temporary, target)
|
||||
finally:
|
||||
temporary.unlink(missing_ok=True)
|
||||
|
||||
|
||||
def extract(archive, destination, windows):
|
||||
required = {"ffmpeg.exe", "ffprobe.exe"} if windows else {"ffmpeg", "ffprobe"}
|
||||
found = set()
|
||||
def target_for(name):
|
||||
# Never extract arbitrary paths: selected basenames map to fixed targets.
|
||||
basename = name.replace("\\", "/").split("/")[-1]
|
||||
if basename in required:
|
||||
if basename in found:
|
||||
raise ValueError("Duplicate decoder executable")
|
||||
found.add(basename)
|
||||
return destination / "bin" / basename
|
||||
if basename.lower().startswith(("license", "copying")) or basename.lower() in {"readme.txt", "readme.md"}:
|
||||
parts = name.replace("\\", "/").split("/")
|
||||
if any(part in {".", "..", ""} or ":" in part for part in parts):
|
||||
raise ValueError("Unsafe notice path")
|
||||
return destination / "licenses" / Path(*parts)
|
||||
return None
|
||||
if archive.suffix == ".zip":
|
||||
with zipfile.ZipFile(archive) as package:
|
||||
for member in package.infolist():
|
||||
target = target_for(member.filename)
|
||||
if target is not None and not member.is_dir():
|
||||
with package.open(member) as stream:
|
||||
install(stream, target, member.file_size)
|
||||
else:
|
||||
with tarfile.open(archive, "r:xz") as package:
|
||||
for member in package:
|
||||
target = target_for(member.name)
|
||||
if target is not None:
|
||||
if not member.isfile():
|
||||
raise ValueError("Selected archive member is not a regular file")
|
||||
with package.extractfile(member) as stream:
|
||||
install(stream, target, member.size)
|
||||
if found != required:
|
||||
raise ValueError("Archive is missing required decoder tools")
|
||||
|
||||
|
||||
def main():
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("--stage", action="store_true", help="Copy verified decoder tools and notices into Tauri resources")
|
||||
args = parser.parse_args()
|
||||
manifest = json.loads(Path(__file__).with_name("manifest.json").read_text())
|
||||
machine = platform.machine().lower()
|
||||
architecture = "aarch64" if machine in {"arm64", "aarch64"} else "x86_64" if machine in {"amd64", "x86_64"} else machine
|
||||
system = platform.system().lower()
|
||||
key = f"{system}-{architecture}"
|
||||
if key not in manifest["platforms"]:
|
||||
raise RuntimeError(f"No verified decoder artifact for {key}; macOS packaging remains a release gate")
|
||||
artifact = manifest["platforms"][key]
|
||||
directory = ROOT / ".searchi/media-runtime"
|
||||
directory.mkdir(parents=True, exist_ok=True)
|
||||
archive = directory / artifact["archive"]
|
||||
if not verified(archive, artifact):
|
||||
url = f"https://github.com/{manifest['provider']}/releases/download/{manifest['release']}/{artifact['archive']}"
|
||||
with tempfile.NamedTemporaryFile(dir=directory, delete=False) as output:
|
||||
temporary = Path(output.name)
|
||||
try:
|
||||
with urllib.request.urlopen(urllib.request.Request(url, headers={"User-Agent": "searchi-runtime-setup"}), timeout=120) as response:
|
||||
copied = 0
|
||||
while block := response.read(65536):
|
||||
copied += len(block)
|
||||
if copied > artifact["bytes"]:
|
||||
raise ValueError("Decoder download exceeds manifest size")
|
||||
output.write(block)
|
||||
output.close()
|
||||
if not verified(temporary, artifact):
|
||||
raise ValueError("Decoder size/SHA-256 mismatch")
|
||||
os.replace(temporary, archive)
|
||||
finally:
|
||||
output.close()
|
||||
temporary.unlink(missing_ok=True)
|
||||
extract(archive, directory, system == "windows")
|
||||
if args.stage:
|
||||
destination = ROOT / "apps/desktop/src-tauri/resources/media-runtime"
|
||||
shutil.copytree(directory / "bin", destination / "bin", dirs_exist_ok=True)
|
||||
shutil.copytree(directory / "licenses", destination / "licenses", dirs_exist_ok=True)
|
||||
shutil.copy2(Path(__file__).with_name("manifest.json"), destination / "manifest.json")
|
||||
print(f"Verified media tools available at {directory / 'bin'}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,60 @@
|
||||
import hashlib
|
||||
import importlib.util
|
||||
from pathlib import Path
|
||||
import tempfile
|
||||
import unittest
|
||||
import zipfile
|
||||
|
||||
spec = importlib.util.spec_from_file_location("media_provision", Path(__file__).with_name("provision.py"))
|
||||
provision = importlib.util.module_from_spec(spec)
|
||||
spec.loader.exec_module(provision)
|
||||
|
||||
|
||||
class ProvisionTests(unittest.TestCase):
|
||||
def package(self, directory, entries):
|
||||
archive = directory / "sample.zip"
|
||||
with zipfile.ZipFile(archive, "w") as package:
|
||||
for name, data in entries:
|
||||
package.writestr(name, data)
|
||||
return archive
|
||||
|
||||
def test_selects_only_tools_and_preserves_distinct_notices(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
directory = Path(temporary)
|
||||
archive = self.package(directory, [("build/bin/ffmpeg.exe", b"tool1"),
|
||||
("build/bin/ffprobe.exe", b"tool2"), ("build/README.txt", b"readme"),
|
||||
("build/LICENSE", b"first license"), ("build/dependency/LICENSE", b"second license"),
|
||||
("../../unselected.exe", b"unselected")])
|
||||
target = directory / "out"
|
||||
provision.extract(archive, target, True)
|
||||
self.assertEqual((target / "bin/ffmpeg.exe").read_bytes(), b"tool1")
|
||||
self.assertEqual(len(list((target / "licenses").rglob("LICENSE"))), 2)
|
||||
self.assertFalse((directory / "unselected.exe").exists())
|
||||
|
||||
def test_missing_and_duplicate_executables_are_explicit_failures(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
directory = Path(temporary)
|
||||
for entries in [[("bin/ffmpeg.exe", b"tool")],
|
||||
[("a/ffmpeg.exe", b"tool"), ("b/ffmpeg.exe", b"second"), ("bin/ffprobe.exe", b"probe")]]:
|
||||
with self.assertRaises(ValueError):
|
||||
provision.extract(self.package(directory, entries), directory / "out", True)
|
||||
|
||||
def test_same_size_corruption_is_not_a_verified_cache(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
path = Path(temporary) / "archive"
|
||||
path.write_bytes(b"good")
|
||||
artifact = {"bytes": 4, "sha256": hashlib.sha256(b"good").hexdigest()}
|
||||
self.assertTrue(provision.verified(path, artifact))
|
||||
path.write_bytes(b"evil")
|
||||
self.assertFalse(provision.verified(path, artifact))
|
||||
|
||||
def test_notice_traversal_cannot_escape_output(self):
|
||||
with tempfile.TemporaryDirectory() as temporary:
|
||||
directory = Path(temporary)
|
||||
archive = self.package(directory, [("../../LICENSE", b"escape")])
|
||||
with self.assertRaises(ValueError):
|
||||
provision.extract(archive, directory / "out", True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in new issue
Block a user