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
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -206,10 +206,11 @@ jobs:
if: >-
needs.scope.outputs.run_suite == 'true' &&
matrix.python-version == '3.14' &&
inputs.package_artifact_name != ''
(needs.scope.outputs.run_desktop == 'true' ||
inputs.package_artifact_name != '')
uses: actions/upload-artifact@v7
with:
name: ${{ inputs.package_artifact_name }}
name: ${{ inputs.package_artifact_name || 'vidxp-python-dist' }}
path: dist/
if-no-files-found: error
retention-days: 30
Expand Down Expand Up @@ -268,10 +269,13 @@ jobs:
if: >-
github.event_name == 'pull_request' &&
needs.scope.outputs.run_desktop == 'true'
needs: scope
needs:
- scope
- validate
uses: ./.github/workflows/desktop.yml
with:
checkout_ref: ${{ github.sha }}
package_artifact_name: ${{ inputs.package_artifact_name || 'vidxp-python-dist' }}

required:
if: always() && github.event_name == 'pull_request'
Expand Down
34 changes: 34 additions & 0 deletions .github/workflows/desktop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ on:
required: false
default: false
type: boolean
package_artifact_name:
description: Validated Python distribution to embed in every installer.
required: false
default: ""
type: string
workflow_dispatch:
inputs:
checkout_ref:
Expand All @@ -34,6 +39,11 @@ on:
required: false
default: false
type: boolean
package_artifact_name:
description: Existing Python distribution artifact; otherwise build from checkout.
required: false
default: ""
type: string
permissions:
contents: read

Expand All @@ -55,6 +65,30 @@ jobs:
with:
ref: ${{ inputs.checkout_ref || github.event.inputs.checkout_ref || github.sha }}

- uses: actions/download-artifact@v8
if: inputs.package_artifact_name != ''
with:
name: ${{ inputs.package_artifact_name }}
path: dist/

- uses: actions/setup-python@v7
if: inputs.package_artifact_name == ''
with:
python-version: "3.14"

- name: Build the Python distribution for a standalone Desktop build
if: inputs.package_artifact_name == ''
shell: bash
run: |
python -m pip install -r utils/build-requirements.txt
bash utils/build_package.sh

- name: Verify the embedded Python package input
shell: bash
run: |
[[ "$(find dist -maxdepth 1 -name '*.whl' | wc -l | tr -d ' ')" == "1" ]]
[[ "$(find dist -maxdepth 1 -name '*.tar.gz' | wc -l | tr -d ' ')" == "1" ]]

- name: Install Linux desktop build dependencies
if: runner.os == 'Linux'
run: |
Expand Down
5 changes: 4 additions & 1 deletion .github/workflows/release-candidate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,11 +106,14 @@ jobs:
run_containers: false

desktop:
needs: contract
needs:
- contract
- core
uses: ./.github/workflows/desktop.yml
with:
artifact_retention_days: 30
checkout_ref: ${{ inputs.head_sha }}
package_artifact_name: vidxp-python-dist
sign: true
secrets: inherit

Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -179,14 +179,18 @@ plugin packaging for additional ChatGPT surfaces will follow separately.
First setup downloads only the models needed for the capabilities you select.
VidXP shows the download size and destination before it starts.

The Desktop-managed Python runtime and its selected dependencies can use
approximately 3 GiB.

| Capability | Approximate model download |
|---|---:|
| Dialogue search | 2.64 GiB |
| Scene search | 1.43 GiB |
| Actor matching | 37 MiB |

Leave additional space for the VidXP runtime, indexes, source videos, and
exported results.
A full local Desktop setup with every search capability uses approximately
7.1 GiB. Leave additional temporary space during installation and for indexes,
source videos, and exported results.

By default, the CLI and desktop app share the same VidXP data directory:

Expand Down
67 changes: 64 additions & 3 deletions desktop/src-tauri/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,53 @@ fn main() {
let expected = manifest["uv_version"]
.as_str()
.expect("runtime manifest must contain uv_version");
let package_name = manifest["package_name"]
.as_str()
.expect("runtime manifest must contain package_name");
let package_version = manifest["package_version"]
.as_str()
.expect("runtime manifest must contain package_version");
let wheel_version = package_version.replace("-b.", "b").replace("-b", "b");
let wheel_prefix = format!("{}-{wheel_version}-", package_name.replace('-', "_"));
let distribution_directory = Path::new("../..").join("dist");
let wheels = std::fs::read_dir(&distribution_directory)
.unwrap_or_else(|error| {
panic!(
"{} is unavailable; build the Python distribution before Desktop: {error}",
distribution_directory.display()
)
})
.map(|entry| {
entry
.expect("the distribution directory must be readable")
.path()
})
.filter(|path| {
path.file_name()
.and_then(|name| name.to_str())
.is_some_and(|name| name.starts_with(&wheel_prefix) && name.ends_with(".whl"))
})
.collect::<Vec<_>>();
assert_eq!(
wheels.len(),
1,
"Desktop requires exactly one {package_name} {package_version} wheel in {}; found {}",
distribution_directory.display(),
wheels.len()
);
let wheel = &wheels[0];
let wheel_name = wheel
.file_name()
.and_then(|name| name.to_str())
.expect("the runtime wheel name must be valid UTF-8");
let wheel_bytes = std::fs::read(wheel).expect("the runtime wheel must be readable");
let wheel_digest =
Sha256::digest(&wheel_bytes)
.iter()
.fold(String::with_capacity(64), |mut encoded, byte| {
write!(&mut encoded, "{byte:02x}").expect("writing to a string cannot fail");
encoded
});
let target = std::env::var("TARGET").expect("Cargo must provide TARGET");
let suffix = if target.contains("windows") {
".exe"
Expand All @@ -34,9 +81,22 @@ fn main() {
expected
);

let constraints =
PathBuf::from(std::env::var_os("OUT_DIR").expect("Cargo must provide OUT_DIR"))
.join("runtime-constraints.txt");
let output_directory =
PathBuf::from(std::env::var_os("OUT_DIR").expect("Cargo must provide OUT_DIR"));
let constraints = output_directory.join("runtime-constraints.txt");
std::fs::write(output_directory.join("runtime-package.whl"), &wheel_bytes)
.expect("Cargo must be able to embed the runtime wheel");
std::fs::write(
output_directory.join("runtime-package-name.txt"),
wheel_name,
)
.expect("Cargo must be able to embed the runtime wheel name");
std::fs::write(
output_directory.join("runtime-package-sha256.txt"),
&wheel_digest,
)
.expect("Cargo must be able to embed the runtime wheel digest");
manifest["package_wheel_sha256"] = serde_json::Value::String(wheel_digest);
let project = Path::new("../..");
let export = std::process::Command::new(&sidecar)
.args([
Expand Down Expand Up @@ -90,6 +150,7 @@ fn main() {
.expect("Cargo must be able to write the embedded runtime manifest");
println!("cargo:rerun-if-changed=../../pyproject.toml");
println!("cargo:rerun-if-changed=../../uv.lock");
println!("cargo:rerun-if-changed=../../dist");
println!("cargo:rerun-if-changed=../runtime-manifest.json");

let attributes = tauri_build::Attributes::new();
Expand Down
Loading