Skip to content

Commit 5be008d

Browse files
committed
Switch macOS installer to guided .pkg flow
1 parent dd5fb86 commit 5be008d

5 files changed

Lines changed: 389 additions & 14 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
name: Build Installers
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
pull_request:
8+
workflow_dispatch:
9+
10+
jobs:
11+
build:
12+
name: Build ${{ matrix.platform }} installer
13+
runs-on: ${{ matrix.runner }}
14+
strategy:
15+
fail-fast: false
16+
matrix:
17+
include:
18+
- platform: macos
19+
runner: macos-latest
20+
- platform: windows
21+
runner: windows-latest
22+
23+
steps:
24+
- name: Checkout
25+
uses: actions/checkout@v4
26+
27+
- name: Setup Python
28+
uses: actions/setup-python@v5
29+
with:
30+
python-version: '3.11'
31+
32+
- name: Install dependencies
33+
run: |
34+
python -m pip install --upgrade pip
35+
python -m pip install . pyinstaller
36+
37+
- name: Build installer bundle
38+
run: python scripts/build_installers.py --platform ${{ matrix.platform }}
39+
40+
- name: Upload installer artifact
41+
uses: actions/upload-artifact@v4
42+
with:
43+
name: openbeat-${{ matrix.platform }}-installer
44+
path: |
45+
dist/installers/*.pkg
46+
dist/installers/*installer.exe
47+
if-no-files-found: error

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@ Music for Test/
1717
__pycache__/
1818
*.pyc
1919
*.egg-info/
20+
build/
21+
dist/
2022

2123
# Generated OpenBeat outputs
2224
*.openbeat-clicks.wav

README.md

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,19 @@ Available actions:
2727

2828
## Installation
2929

30-
### 1. Create the Python environment
30+
### Option A (recommended): single-file installer download (no Python required)
31+
32+
CI now builds platform-specific single-file installers that bundle the OpenBeat CLI runtime:
33+
34+
- macOS artifact: `OpenBeat-macos-<version>.pkg`
35+
- Windows artifact: `OpenBeat-windows-<version>-installer.exe`
36+
37+
On macOS, run the `.pkg` in Installer.app (multi-step guided setup). On Windows, run the installer `.exe` directly.
38+
Restart Resolve after install.
39+
40+
### Option B: developer install from source
41+
42+
#### 1. Create the Python environment
3143

3244
```bash
3345
cd /Users/parker/Documents/Code/OpenBeat
@@ -36,7 +48,7 @@ python3 -m venv .venv
3648
python -m pip install -e .
3749
```
3850

39-
### 2. Link the Resolve scripts
51+
#### 2. Link the Resolve scripts
4052

4153
```bash
4254
cd /Users/parker/Documents/Code/OpenBeat
@@ -45,6 +57,17 @@ cd /Users/parker/Documents/Code/OpenBeat
4557

4658
Restart Resolve after linking the scripts if it is already open.
4759

60+
## Building installers locally
61+
62+
Install build dependencies, then run:
63+
64+
```bash
65+
python -m pip install . pyinstaller
66+
python scripts/build_installers.py --platform all
67+
```
68+
69+
Generated installer archives are written to `dist/installers/`.
70+
4871
## Outputs
4972

5073
- Click tracks are written next to the source file:

resolve/Fusion/Modules/OpenBeat/OpenBeatCommon.lua

Lines changed: 64 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,24 @@
11
local OpenBeat = {}
22

3+
local function is_windows()
4+
return package.config:sub(1, 1) == "\\"
5+
end
6+
7+
local function path_sep()
8+
if is_windows() then
9+
return "\\"
10+
end
11+
return "/"
12+
end
13+
314
local function dirname(path)
4-
return path:match("^(.*)/[^/]+$") or "."
15+
local normalized = tostring(path):gsub("\\", "/")
16+
return normalized:match("^(.*)/[^/]+$") or "."
517
end
618

719
local function join_path(...)
820
local items = { ... }
9-
return table.concat(items, "/")
21+
return table.concat(items, path_sep())
1022
end
1123

1224
local function file_exists(path)
@@ -29,7 +41,11 @@ local function first_value(items)
2941
end
3042

3143
local function shell_quote(value)
32-
return "'" .. tostring(value):gsub("'", [['"'"']]) .. "'"
44+
local as_string = tostring(value)
45+
if is_windows() then
46+
return '"' .. as_string:gsub('"', '\\"') .. '"'
47+
end
48+
return "'" .. as_string:gsub("'", [['"'"']]) .. "'"
3349
end
3450

3551
local function current_script_path()
@@ -66,7 +82,25 @@ local function repo_root()
6682
end
6783

6884
local function log(message)
69-
local path = os.getenv("HOME") .. "/Library/Application Support/Blackmagic Design/DaVinci Resolve/logs/OpenBeat.log"
85+
local home = os.getenv("HOME")
86+
if not home or home == "" then
87+
home = os.getenv("USERPROFILE")
88+
end
89+
if not home or home == "" then
90+
return
91+
end
92+
93+
local path
94+
if is_windows() then
95+
local appdata = os.getenv("APPDATA")
96+
if appdata and appdata ~= "" then
97+
path = join_path(appdata, "Blackmagic Design", "DaVinci Resolve", "Support", "logs", "OpenBeat.log")
98+
else
99+
path = join_path(home, "AppData", "Roaming", "Blackmagic Design", "DaVinci Resolve", "Support", "logs", "OpenBeat.log")
100+
end
101+
else
102+
path = join_path(home, "Library", "Application Support", "Blackmagic Design", "DaVinci Resolve", "logs", "OpenBeat.log")
103+
end
70104
local handle = io.open(path, "a")
71105
if not handle then
72106
return
@@ -89,20 +123,38 @@ end
89123

90124
local function temp_path(prefix, extension)
91125
local name = string.format("%s_%d_%d%s", prefix, os.time(), math.random(1000, 9999), extension or "")
92-
return "/tmp/" .. name
126+
local base = os.getenv("TMPDIR") or os.getenv("TEMP") or os.getenv("TMP")
127+
if not base or base == "" then
128+
base = is_windows() and "C:\\Windows\\Temp" or "/tmp"
129+
end
130+
return join_path(base, name)
93131
end
94132

95133
local function python_bin()
96134
if local_config.python_bin and file_exists(local_config.python_bin) then
97135
return local_config.python_bin
98136
end
99-
local candidate = join_path(repo_root(), ".venv", "bin", "python")
100-
if file_exists(candidate) then
101-
return candidate
137+
local candidates = {
138+
join_path(repo_root(), ".venv", "bin", "python"),
139+
join_path(repo_root(), ".venv", "Scripts", "python.exe"),
140+
}
141+
for _, candidate in ipairs(candidates) do
142+
if file_exists(candidate) then
143+
return candidate
144+
end
102145
end
103146
return "python3"
104147
end
105148

149+
local function command_prefix()
150+
local bin = python_bin()
151+
local lowered = string.lower(bin)
152+
if lowered:match("python%.exe$") or lowered:match("python$") then
153+
return shell_quote(bin) .. " -m openbeat.cli"
154+
end
155+
return shell_quote(bin)
156+
end
157+
106158
local function project_context()
107159
local resolve_app = resolve or app:GetResolve()
108160
if not resolve_app then
@@ -260,8 +312,8 @@ end
260312

261313
local function analyze_source(source_path)
262314
local output = temp_path("openbeat_analysis", ".lua")
263-
local final_command = shell_quote(python_bin())
264-
.. " -m openbeat.cli analyze --audio "
315+
local final_command = command_prefix()
316+
.. " analyze --audio "
265317
.. shell_quote(source_path)
266318
.. " --format lua --output "
267319
.. shell_quote(output)
@@ -348,8 +400,8 @@ end
348400
local function render_click_track(source_path, mode)
349401
local suffix = mode == "raw" and ".openbeat-raw-clicks" or ".openbeat-clicks"
350402
local output = output_path_for(source_path, suffix, ".wav")
351-
local command = shell_quote(python_bin())
352-
.. " -m openbeat.cli click-track --audio "
403+
local command = command_prefix()
404+
.. " click-track --audio "
353405
.. shell_quote(source_path)
354406
.. " --mode "
355407
.. shell_quote(mode)

0 commit comments

Comments
 (0)