Skip to content

Commit b70e3ad

Browse files
BackBack
authored andcommitted
Add update checks and release installer workflow
1 parent 835695d commit b70e3ad

17 files changed

Lines changed: 1209 additions & 18 deletions
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
name: Build release installers
2+
3+
on:
4+
workflow_dispatch:
5+
inputs:
6+
tag:
7+
description: "Release tag to upload installers to"
8+
required: true
9+
default: "v1.0.0"
10+
11+
permissions:
12+
contents: write
13+
14+
jobs:
15+
mac:
16+
name: macOS arm64 installers
17+
runs-on: macos-14
18+
defaults:
19+
run:
20+
working-directory: Mac
21+
steps:
22+
- uses: actions/checkout@v4
23+
- uses: actions/setup-node@v4
24+
with:
25+
node-version: "20"
26+
cache: npm
27+
cache-dependency-path: Mac/package-lock.json
28+
- run: npm ci
29+
- run: npm run build
30+
- run: npx electron-builder --mac dmg pkg --arm64 --config.directories.output=release-upload
31+
- name: Upload macOS assets
32+
env:
33+
GH_TOKEN: ${{ github.token }}
34+
run: |
35+
gh release upload "${{ inputs.tag }}" \
36+
release-upload/DL-Editor-Mac-1.0.0-arm64.dmg \
37+
release-upload/DL-Editor-Mac-1.0.0-arm64.pkg \
38+
release-upload/DL-Editor-Mac-1.0.0-arm64.dmg.blockmap \
39+
--clobber \
40+
--repo "${{ github.repository }}"
41+
42+
windows:
43+
name: Windows x64 installer
44+
runs-on: windows-latest
45+
defaults:
46+
run:
47+
working-directory: Windows
48+
steps:
49+
- uses: actions/checkout@v4
50+
- uses: actions/setup-node@v4
51+
with:
52+
node-version: "20"
53+
cache: npm
54+
cache-dependency-path: Windows/package-lock.json
55+
- run: npm ci
56+
- run: npm run dist
57+
- name: Upload Windows assets
58+
env:
59+
GH_TOKEN: ${{ github.token }}
60+
run: |
61+
gh release upload "${{ inputs.tag }}" `
62+
"release/DL-Editor-Windows-Setup-1.0.0.exe" `
63+
"release/DL-Editor-Windows-Setup-1.0.0.exe.blockmap" `
64+
--clobber `
65+
--repo "${{ github.repository }}"
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
const assert = require("assert");
2+
3+
const { deriveStartTimeMs, findMediaTimestampMs } = require("../src/main/mediaStartTime.cjs");
4+
5+
const endTimeMs = Date.parse("2026-06-26T08:30:00.000Z");
6+
const metadataStartMs = Date.parse("2026-06-26T08:00:00.000Z");
7+
8+
assert.equal(
9+
findMediaTimestampMs({
10+
format: { tags: { creation_time: "2026-06-26T08:00:00.000000Z" } },
11+
streams: []
12+
}),
13+
metadataStartMs
14+
);
15+
16+
assert.equal(
17+
findMediaTimestampMs({
18+
format: { tags: {} },
19+
streams: [{ tags: { creation_time: "2026-06-26T08:00:00.000000Z" } }]
20+
}),
21+
metadataStartMs
22+
);
23+
24+
assert.equal(
25+
deriveStartTimeMs({
26+
mediaInfo: {
27+
duration: 1800,
28+
format: { tags: { creation_time: "2026-06-26T08:00:00.000000Z" } },
29+
streams: []
30+
},
31+
modifiedAtMs: endTimeMs
32+
}),
33+
metadataStartMs
34+
);
35+
36+
assert.equal(
37+
deriveStartTimeMs({
38+
mediaInfo: {
39+
duration: 1800,
40+
format: { tags: {} },
41+
streams: []
42+
},
43+
modifiedAtMs: endTimeMs
44+
}),
45+
endTimeMs - 1800 * 1000
46+
);
47+
48+
assert.equal(
49+
deriveStartTimeMs({
50+
mediaInfo: {
51+
duration: 0,
52+
format: { tags: {} },
53+
streams: []
54+
},
55+
modifiedAtMs: endTimeMs
56+
}),
57+
endTimeMs
58+
);
59+
60+
console.log("Media start time tests passed.");
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
const assert = require("assert");
2+
3+
const { buildNoReleaseResult, buildUpdateResult, compareVersions, selectDownloadAsset } = require("../src/main/updateChecker.cjs");
4+
5+
const release = {
6+
tag_name: "v1.2.0",
7+
html_url: "https://github.com/infera-AI/DL-Editor/releases/tag/v1.2.0",
8+
name: "DL Editor 1.2.0",
9+
published_at: "2026-06-26T08:00:00Z",
10+
assets: [
11+
{
12+
name: "DL-Editor-Mac-1.2.0-arm64.pkg",
13+
browser_download_url: "https://github.com/infera-AI/DL-Editor/releases/download/v1.2.0/pkg"
14+
},
15+
{
16+
name: "DL-Editor-Mac-1.2.0-arm64.dmg",
17+
browser_download_url: "https://github.com/infera-AI/DL-Editor/releases/download/v1.2.0/dmg"
18+
},
19+
{
20+
name: "DL-Editor-Windows-Setup-1.2.0.exe",
21+
browser_download_url: "https://github.com/infera-AI/DL-Editor/releases/download/v1.2.0/exe"
22+
},
23+
{
24+
name: "DL-Editor-Windows-Setup-1.2.0.exe.blockmap",
25+
browser_download_url: "https://github.com/infera-AI/DL-Editor/releases/download/v1.2.0/blockmap"
26+
}
27+
]
28+
};
29+
30+
assert.equal(compareVersions("1.2.0", "1.1.9"), 1);
31+
assert.equal(compareVersions("v1.0.0", "1.0.0"), 0);
32+
assert.equal(compareVersions("1.0.0", "1.0.1"), -1);
33+
34+
assert.equal(selectDownloadAsset(release, "darwin").name, "DL-Editor-Mac-1.2.0-arm64.dmg");
35+
assert.equal(selectDownloadAsset(release, "win32").name, "DL-Editor-Windows-Setup-1.2.0.exe");
36+
37+
const macUpdate = buildUpdateResult({
38+
currentVersion: "1.0.0",
39+
platform: "darwin",
40+
release
41+
});
42+
assert.equal(macUpdate.status, "available");
43+
assert.equal(macUpdate.latestVersion, "1.2.0");
44+
assert.equal(macUpdate.downloadUrl, "https://github.com/infera-AI/DL-Editor/releases/download/v1.2.0/dmg");
45+
46+
const current = buildUpdateResult({
47+
currentVersion: "1.2.0",
48+
platform: "win32",
49+
release
50+
});
51+
assert.equal(current.status, "latest");
52+
assert.equal(current.downloadUrl, "");
53+
54+
const noRelease = buildNoReleaseResult({ currentVersion: "1.0.0" });
55+
assert.equal(noRelease.status, "no_release");
56+
assert.equal(noRelease.currentVersion, "1.0.0");
57+
assert.equal(noRelease.releaseUrl, "https://github.com/infera-AI/DL-Editor/releases");
58+
59+
console.log("Update checker tests passed.");

Mac/src/main/main.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ const os = require("os");
66
const path = require("path");
77
const { getMuxOriginalAudioArgs, getOutputStreamArgs } = require("./ffmpegArgs.cjs");
88
const { parseMacGpuUsageFromIoreg } = require("./gpuUsage.cjs");
9+
const { deriveStartTimeMs } = require("./mediaStartTime.cjs");
10+
const { checkForUpdate } = require("./updateChecker.cjs");
911
const {
1012
getFilterThreadCount,
1113
getSegmentCount,
@@ -471,9 +473,16 @@ function formatBytes(bytes) {
471473
return `${size.toFixed(size >= 10 || index === 0 ? 0 : 1)} ${units[index]}`;
472474
}
473475

474-
function getFileMetadata(filePath) {
476+
async function getFileMetadata(filePath) {
475477
const stats = fs.statSync(filePath);
476478
const modifiedAtMs = Number.isFinite(stats.mtimeMs) ? stats.mtimeMs : Date.now();
479+
let mediaInfo = null;
480+
481+
try {
482+
mediaInfo = await probeMediaInfo(filePath);
483+
} catch {
484+
mediaInfo = null;
485+
}
477486

478487
return {
479488
id: crypto.randomUUID(),
@@ -483,7 +492,7 @@ function getFileMetadata(filePath) {
483492
sizeLabel: formatBytes(stats.size),
484493
modifiedAt: new Date(modifiedAtMs).toISOString(),
485494
modifiedAtMs,
486-
startTimeMs: modifiedAtMs,
495+
startTimeMs: deriveStartTimeMs({ mediaInfo, modifiedAtMs }),
487496
status: "queued",
488497
progress: 0
489498
};
@@ -560,15 +569,17 @@ async function probeMediaInfo(inputPath) {
560569
"-select_streams",
561570
"v:0",
562571
"-show_entries",
563-
"stream=width,height,avg_frame_rate,r_frame_rate:format=duration",
572+
"stream=width,height,avg_frame_rate,r_frame_rate:stream_tags:format=duration:format_tags",
564573
"-of",
565574
"json",
566575
inputPath
567576
]);
568577

569578
const parsed = JSON.parse(stdout || "{}");
570-
const stream = Array.isArray(parsed.streams) ? parsed.streams[0] || {} : {};
571-
const duration = Number(parsed.format?.duration);
579+
const streams = Array.isArray(parsed.streams) ? parsed.streams : [];
580+
const format = parsed.format || {};
581+
const stream = streams[0] || {};
582+
const duration = Number(format.duration);
572583
const width = Number(stream.width);
573584
const height = Number(stream.height);
574585

@@ -577,7 +588,9 @@ async function probeMediaInfo(inputPath) {
577588
width: Number.isFinite(width) && width > 0 ? width : null,
578589
height: Number.isFinite(height) && height > 0 ? height : null,
579590
avgFrameRate: stream.avg_frame_rate || null,
580-
rFrameRate: stream.r_frame_rate || null
591+
rFrameRate: stream.r_frame_rate || null,
592+
format,
593+
streams
581594
};
582595
}
583596

@@ -1288,7 +1301,7 @@ ipcMain.handle("videos:select", async () => {
12881301
return [];
12891302
}
12901303

1291-
return result.filePaths.map(getFileMetadata);
1304+
return Promise.all(result.filePaths.map(getFileMetadata));
12921305
});
12931306

12941307
ipcMain.handle("output:select-directory", async () => {
@@ -1304,6 +1317,13 @@ ipcMain.handle("output:select-directory", async () => {
13041317
ipcMain.handle("system:get-capabilities", async () => getCapabilities());
13051318
ipcMain.handle("system:get-usage", async () => latestUsage || buildUsageSnapshot());
13061319

1320+
ipcMain.handle("updates:check", async () =>
1321+
checkForUpdate({
1322+
currentVersion: app.getVersion(),
1323+
platform: process.platform
1324+
})
1325+
);
1326+
13071327
ipcMain.handle("transcode:start-batch", async (_event, payload) => {
13081328
if (queueBusy) {
13091329
throw new Error("A batch is already running.");
@@ -1393,3 +1413,13 @@ ipcMain.handle("shell:open-path", async (_event, targetPath) => {
13931413
}
13941414
return "Path does not exist.";
13951415
});
1416+
1417+
ipcMain.handle("shell:open-external", async (_event, targetUrl) => {
1418+
const url = String(targetUrl || "");
1419+
if (!/^https:\/\/github\.com\/infera-AI\/DL-Editor\/releases(?:\/|$)/.test(url)) {
1420+
throw new Error("Unsupported update URL.");
1421+
}
1422+
1423+
await shell.openExternal(url);
1424+
return { opened: true };
1425+
});

0 commit comments

Comments
 (0)