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: 12 additions & 0 deletions .agents/skills/vlc-runtime-check/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,18 @@ Copy-Item .\stage\plugins\video_filter\* "C:\Program Files\VideoLAN\VLC\plugins\
& "C:\Program Files\VideoLAN\VLC\vlc.exe" -vvv --file-logging --logfile=vlc-installed-test.log --video-filter=icop .\sample.mp4
```

## macOS Flow

```sh
sh tools/verify_macos_package.sh releases/v<version>/mac arm64
sh tools/macos_vlc_smoke_test.sh /Applications/VLC.app sample.mp4 releases/v<version>/mac/plugins
sh releases/v<version>/mac/install_icop_plugin.sh --dry-run
```

- The plugin must be `libicop_plugin.dylib`; VLC for macOS ignores `.so`.
- The payload architecture must match VLC.app (`lipo -archs`), not the shell.
- Downloaded payloads carry `com.apple.quarantine`, which blocks `dlopen`.

## Failure Checklist

- Missing side-by-side DLLs
Expand Down
16 changes: 16 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,14 @@ jobs:
run: cmake --build build-ci --target icop_plugin icop_core icop_test -j 2
- name: Test
run: ctest --test-dir build-ci --output-on-failure
- name: Check VLC registers the plugin
run: |
brew install --cask vlc
xattr -dr com.apple.quarantine /Applications/VLC.app || true
mkdir -p vlc-plugin-check/video_filter
cp build-ci/libicop_plugin.dylib build-ci/libicop_core.dylib vlc-plugin-check/video_filter/
VLC_PLUGIN_PATH="$PWD/vlc-plugin-check" /Applications/VLC.app/Contents/MacOS/VLC -I dummy --list > vlc-modules.log 2>&1 || true
grep -E '^[[:space:]]+icop[[:space:]]' vlc-modules.log

macos-arm64:
name: macOS ARM64
Expand Down Expand Up @@ -170,3 +178,11 @@ jobs:
run: cmake --build build-ci --target icop_plugin icop_core icop_test -j 2
- name: Test
run: ctest --test-dir build-ci --output-on-failure
- name: Check VLC registers the plugin
run: |
brew install --cask vlc
xattr -dr com.apple.quarantine /Applications/VLC.app || true
mkdir -p vlc-plugin-check/video_filter
cp build-ci/libicop_plugin.dylib build-ci/libicop_core.dylib vlc-plugin-check/video_filter/
VLC_PLUGIN_PATH="$PWD/vlc-plugin-check" /Applications/VLC.app/Contents/MacOS/VLC -I dummy --list > vlc-modules.log 2>&1 || true
grep -E '^[[:space:]]+icop[[:space:]]' vlc-modules.log
38 changes: 38 additions & 0 deletions .github/workflows/homebrew-tap.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Homebrew tap

# Asks asayed18/homebrew-icop to render, test and publish Casks/icop.rb for a
# newly published release. The tap also polls every six hours, so a missing
# HOMEBREW_TAP_TOKEN only delays the update. The token needs Contents
# read/write access to the tap repository.
on:
release:
types: [published]
workflow_dispatch:
inputs:
version:
description: 'Release version to publish to the tap (e.g. 0.1.7)'
required: true
type: string

permissions:
contents: read

jobs:
notify:
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Dispatch tap update
env:
TAP_TOKEN: ${{ secrets.HOMEBREW_TAP_TOKEN }}
VERSION: ${{ inputs.version || github.event.release.tag_name }}
run: |
if [ -z "$TAP_TOKEN" ]; then
echo "HOMEBREW_TAP_TOKEN is not set; the tap's scheduled check will pick up v${VERSION#v}."
exit 0
fi
curl -fsS -X POST \
-H "Authorization: Bearer $TAP_TOKEN" \
-H "Accept: application/vnd.github+json" \
https://api.github.com/repos/asayed18/homebrew-icop/dispatches \
-d "{\"event_type\":\"icop-release\",\"client_payload\":{\"version\":\"${VERSION#v}\"}}"
38 changes: 38 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,25 @@ jobs:
-DNSFW_INSTALL_VLC_PLUGIN=OFF
- name: Package
run: cmake --build build-ci --target icop_package -j $(sysctl -n hw.logicalcpu)
- name: Verify macOS package
run: sh tools/verify_macos_package.sh "releases/v${ICOP_VERSION}/mac" x86_64 14.0
- name: Install VLC from Homebrew
run: |
brew install --cask vlc
xattr -dr com.apple.quarantine /Applications/VLC.app || true
- name: Check bundled installer against Homebrew VLC
run: sh "releases/v${ICOP_VERSION}/mac/install_icop_plugin.sh" --dry-run
- name: Smoke test icop in VLC
run: >-
sh tools/macos_vlc_smoke_test.sh /Applications/VLC.app sample.mp4
"releases/v${ICOP_VERSION}/mac/plugins" vlc-macos-smoke.log
- name: Upload VLC smoke logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: vlc-macos-x86_64-smoke-logs
path: vlc-macos-smoke*.log
if-no-files-found: ignore
- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
Expand Down Expand Up @@ -355,6 +374,25 @@ jobs:
-DNSFW_INSTALL_VLC_PLUGIN=OFF
- name: Package
run: cmake --build build-ci --target icop_package -j $(sysctl -n hw.logicalcpu)
- name: Verify macOS package
run: sh tools/verify_macos_package.sh "releases/v${ICOP_VERSION}/mac" arm64 14.0
- name: Install VLC from Homebrew
run: |
brew install --cask vlc
xattr -dr com.apple.quarantine /Applications/VLC.app || true
- name: Check bundled installer against Homebrew VLC
run: sh "releases/v${ICOP_VERSION}/mac/install_icop_plugin.sh" --dry-run
- name: Smoke test icop in VLC
run: >-
sh tools/macos_vlc_smoke_test.sh /Applications/VLC.app sample.mp4
"releases/v${ICOP_VERSION}/mac/plugins" vlc-macos-smoke.log
- name: Upload VLC smoke logs
if: failure()
uses: actions/upload-artifact@v4
with:
name: vlc-macos-arm64-smoke-logs
path: vlc-macos-smoke*.log
if-no-files-found: ignore
- name: Upload release artifact
uses: actions/upload-artifact@v4
with:
Expand Down
25 changes: 24 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,30 @@ first, and short hashes identify the commit that introduced each change.

## Unreleased

No unreleased changes are documented yet.
### macOS / Apple Silicon

- Build the macOS plugin as `libicop_plugin.dylib`. VLC for macOS only loads
`lib*_plugin.dylib`, so previous macOS releases were silently ignored.
- Pin the macOS deployment target to 14.0 so packages built on the newest CI
runners still load on older Apple Silicon Macs.
- Package ONNX Runtime 1.23.2 for Intel and universal2 builds, because 1.26.0
has no x86_64 macOS archive and the Intel package shipped without a runtime.
Never pick up a Homebrew `onnxruntime` for macOS packages.
- Ad-hoc sign any packaged dylib whose signature does not verify, with an
optional Developer ID identity.
- The POSIX installer now ships inside Linux/macOS archives and accepts
`--release-dir`. On macOS it finds VLC.app from the DMG, Homebrew cask
(including `--appdir`), `~/Applications`, MacPorts, or Spotlight, and
matches the payload to VLC's architecture (Rosetta aware). It also rejects
VLC 4, clears download quarantine, removes the stale `.so` plugin, and
treats a missing `vlc-cache-gen` as non-fatal.
- Release CI verifies the macOS package and runs a Homebrew VLC smoke test.
PR CI checks that VLC registers the plugin on both macOS architectures.
- Add `install_icop_plugin.sh --uninstall`, and publish releases to the
`asayed18/homebrew-icop` tap (`brew install --cask icop`). A new workflow
notifies the tap when a release is published.
- Bump the version to 0.1.7 so the fixed macOS packages do not reuse the
broken v0.1.6 draft.

## 0.1.4 - 2026-07-17

Expand Down
14 changes: 13 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
cmake_minimum_required(VERSION 3.16)
# Without an explicit floor, Apple toolchains stamp binaries with the build
# host's macOS version, so a package built on the newest CI runner refuses to
# load on older Apple Silicon Macs. 14.0 matches the minimum of the packaged
# osx-arm64 ONNX Runtime; it must be set before project().
if(CMAKE_HOST_APPLE AND NOT DEFINED CMAKE_OSX_DEPLOYMENT_TARGET AND
NOT DEFINED ENV{MACOSX_DEPLOYMENT_TARGET})
set(CMAKE_OSX_DEPLOYMENT_TARGET "14.0" CACHE STRING
"Minimum macOS version for the VLC plugin and detector core")
endif()
project(icop
VERSION 0.1.6
VERSION 0.1.7
DESCRIPTION "Privacy-first sensitive-content filtering for VLC media player"
HOMEPAGE_URL "https://github.com/asayed18/icop"
LANGUAGES C CXX)
Expand Down Expand Up @@ -226,6 +235,9 @@ if(NSFW_BUILD_VLC_MODULE)
target_link_libraries(icop_plugin PRIVATE va va-drm)
endif()
if(APPLE)
# VLC for macOS only scans plugins named lib*_plugin.dylib; CMake's
# default MODULE suffix (.so) makes VLC silently skip the filter.
set_target_properties(icop_plugin PROPERTIES SUFFIX ".dylib")
target_link_options(icop_plugin PRIVATE
"-Wl,-undefined,dynamic_lookup"
)
Expand Down
83 changes: 79 additions & 4 deletions INSTALL.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,13 @@ Get the package for your platform from the
| Windows x86_64 | `icop-v0.1.4-windows-x86_64.7z.001` and subsequent volumes |
| Linux x86_64 | `icop-v0.1.4-linux-x86_64.tar.gz` |
| Linux ARM64 | `icop-v0.1.4-linux-arm64.tar.gz` |
| macOS x86_64 | `icop-v0.1.4-mac-x86_64.tar.gz` |
| macOS ARM64 | `icop-v0.1.4-mac-arm64.tar.gz` |
| macOS Intel (x86_64) | `icop-v0.1.4-mac-x86_64.tar.gz` |
| macOS Apple Silicon (M1/M2/M3/M4, ARM64) | `icop-v0.1.4-mac-arm64.tar.gz` |

On macOS, pick the archive that matches **VLC**, not the Mac: an Apple Silicon
Mac running the Intel-only VLC build needs the x86_64 archive. The bundled
installer (Step 2) detects this for you. Releases up to v0.1.6 shipped a macOS
plugin that VLC could not load; use a newer release.

For the Windows CUDA package, download every `.7z` volume into the same folder
and extract the `.7z.001` file with [7-Zip](https://www.7-zip.org/).
Expand All @@ -26,6 +31,8 @@ and extract the `.7z.001` file with [7-Zip](https://www.7-zip.org/).
> an elevated PowerShell (Run as Administrator). Do not ask the user to perform
> setup steps when they asked you to do the work directly.

On macOS, follow [Step 2 (macOS)](#step-2-macos--install-into-vlcapp) below.

Extract the archive and copy the files from `plugins/video_filter/` into VLC's
plugin directory:

Expand All @@ -47,11 +54,79 @@ After copying, regenerate VLC's plugin cache:
sudo /usr/lib/vlc/vlc-cache-gen /usr/lib/vlc/plugins
```

**macOS**:
## Step 2 (macOS) — Install into VLC.app

Requirements: macOS 14 (Sonoma) or newer and VLC **3.0.x** (VLC 4 nightlies
use a different plugin ABI). All of these VLC installs are supported:

| How VLC was installed | Location the installer finds |
|---|---|
| DMG from [videolan.org](https://www.videolan.org/vlc/download-macosx.html) | `/Applications/VLC.app` |
| `brew install --cask vlc` | `/Applications/VLC.app`, or the `--appdir` in `HOMEBREW_CASK_OPTS` |
| Per-user install | `~/Applications/VLC.app` |
| MacPorts | `/Applications/MacPorts/VLC.app` |
| Anywhere else | Found through Spotlight, or pass `--vlc-root /path/to/VLC.app` |

**Homebrew (recommended):** quit VLC, then run:

```bash
sudo /Applications/VLC.app/Contents/MacOS/vlc-cache-gen /Applications/VLC.app/Contents/MacOS/plugins
brew tap asayed18/icop
brew install --cask icop
```

Use `brew reinstall --cask icop` after a VLC update and
`brew uninstall --cask icop` to remove the plugin. The cask runs the same
installer described below.

**Direct download:** quit VLC, then extract the archive and run the installer
that ships inside it:

```bash
tar xzf icop-v<version>-mac-arm64.tar.gz
sh mac/install_icop_plugin.sh --dry-run # shows which VLC.app and payload it picked
sh mac/install_icop_plugin.sh
```

The installer:

- verifies `SHA256SUMS`, then copies the payload into
`VLC.app/Contents/MacOS/plugins/video_filter/`, asking for `sudo` only if the
bundle is not writable, and rolls back if any step fails;
- checks the VLC.app architecture with `lipo`, so an Intel-only VLC running
under Rosetta on Apple Silicon gets the x86_64 payload (use `--arch` to
override);
- clears the `com.apple.quarantine` flag that browsers add to downloads.
Without this, Gatekeeper refuses to load the dylibs inside VLC;
- removes the unusable `libicop_plugin.so` left by releases up to v0.1.6;
- regenerates VLC's plugin cache when `vlc-cache-gen` is bundled. When it is
not, VLC still loads the new plugin at launch.

If the copy fails with *Operation not permitted*, macOS App Management is
protecting VLC.app. Allow your terminal app under
*System Settings → Privacy & Security → App Management* and rerun.

VLC updates (Sparkle auto-update or `brew upgrade --cask vlc`) replace
VLC.app and remove the plugin, so rerun the installer after each VLC update.

Manual install, if you prefer not to run the script:

```bash
PLUGINS=/Applications/VLC.app/Contents/MacOS/plugins
cp mac/plugins/video_filter/* "$PLUGINS/video_filter/"
xattr -d com.apple.quarantine "$PLUGINS"/video_filter/libicop_* "$PLUGINS"/video_filter/libonnxruntime*.dylib "$PLUGINS"/video_filter/*.onnx 2>/dev/null
rm -f "$PLUGINS/video_filter/libicop_plugin.so"
/Applications/VLC.app/Contents/MacOS/VLC --reset-plugins-cache vlc://quit
```

Start VLC from Terminal once to confirm the filter loads:

```bash
/Applications/VLC.app/Contents/MacOS/VLC --video-filter=icop /path/to/video.mp4 2>&1 | grep '^icop'
```

Expect a line such as `icop: using marqo model profile (...)`. If it reports
`unable to load core DLL`, the plugin files are not all in the same folder.

## Step 3 — Enable the filter

Launch VLC with the icop filter active on the command line:
Expand Down
34 changes: 32 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,10 @@ locally and are not committed.
| macOS x86_64 | Tested | CPU / CoreML |
| macOS ARM64 | Tested | CPU / CoreML |

GPU runtimes are detected automatically at plugin load. Contributions that
improve GPU support or validate the macOS path are welcome.
GPU runtimes are detected automatically at plugin load. macOS packages need
macOS 14 or newer and VLC 3.0.x. Apple Silicon uses ONNX Runtime 1.26; Intel
Macs use ONNX Runtime 1.23.2, the last release Microsoft published for x86_64
macOS. Contributions that improve GPU support are welcome.

## VLC AI Filter Features

Expand Down Expand Up @@ -195,6 +197,34 @@ sh tools/install_icop_plugin.sh --vlc-root /usr
Close VLC before installation. To stop it explicitly, pass
`INSTALL_ARGS=-StopVlc` on Windows or `INSTALL_ARGS=--stop-vlc` on Linux/macOS.

### macOS (Apple Silicon and Intel)

With Homebrew:

```sh
brew tap asayed18/icop
brew install --cask icop
```

The [asayed18/homebrew-icop](https://github.com/asayed18/homebrew-icop) tap
updates itself when an icop release is published. Linux and macOS release
archives also include `install_icop_plugin.sh`, so a downloaded release
installs without a source checkout:

```sh
tar xzf icop-v<version>-mac-arm64.tar.gz
sh mac/install_icop_plugin.sh
```

The installer finds VLC.app wherever it was installed: the videolan.org DMG,
`brew install --cask vlc` (including a custom `--appdir`), `~/Applications`,
MacPorts, or through Spotlight. It matches the payload to the VLC.app
architecture, so an Intel-only VLC running under Rosetta gets the x86_64
build. It also clears the download quarantine flag that would otherwise stop
Gatekeeper from loading the plugin. Rerun it after VLC updates, because updates
replace VLC.app. See [INSTALL.md](INSTALL.md#step-2-macos--install-into-vlcapp)
for manual steps and troubleshooting.

See [CONTRIBUTING.md](CONTRIBUTING.md) for lightweight Windows, Linux, and WSL
build and test commands. See [docs/releasing.md](docs/releasing.md) for package
versioning, checksums, and release verification.
Expand Down
Loading
Loading