feat: wire session bindings through main, ipc, and cli runtime

This commit is contained in:
2026-04-10 02:54:01 -07:00
parent fd6dea9d33
commit 48f74db239
52 changed files with 1931 additions and 426 deletions

View File

@@ -28,6 +28,27 @@ USAGE
force=0
generate_webp=0
input=""
ffmpeg_bin="${FFMPEG_BIN:-ffmpeg}"
normalize_path() {
local value="$1"
if command -v cygpath > /dev/null 2>&1; then
case "$value" in
[A-Za-z]:\\* | [A-Za-z]:/*)
cygpath -u "$value"
return 0
;;
esac
fi
if [[ "$value" =~ ^([A-Za-z]):[\\/](.*)$ ]]; then
local drive="${BASH_REMATCH[1],,}"
local rest="${BASH_REMATCH[2]}"
rest="${rest//\\//}"
printf '/mnt/%s/%s\n' "$drive" "$rest"
return 0
fi
printf '%s\n' "$value"
}
while [[ $# -gt 0 ]]; do
case "$1" in
@@ -63,9 +84,19 @@ if [[ -z "$input" ]]; then
exit 1
fi
if ! command -v ffmpeg > /dev/null 2>&1; then
echo "Error: ffmpeg is not installed or not in PATH." >&2
exit 1
input="$(normalize_path "$input")"
ffmpeg_bin="$(normalize_path "$ffmpeg_bin")"
if [[ "$ffmpeg_bin" == */* ]]; then
if [[ ! -x "$ffmpeg_bin" ]]; then
echo "Error: ffmpeg binary is not executable: $ffmpeg_bin" >&2
exit 1
fi
else
if ! command -v "$ffmpeg_bin" > /dev/null 2>&1; then
echo "Error: ffmpeg is not installed or not in PATH." >&2
exit 1
fi
fi
if [[ ! -f "$input" ]]; then
@@ -102,7 +133,7 @@ fi
has_encoder() {
local encoder="$1"
ffmpeg -hide_banner -encoders 2> /dev/null | awk -v encoder="$encoder" '$2 == encoder { found = 1 } END { exit(found ? 0 : 1) }'
"$ffmpeg_bin" -hide_banner -encoders 2> /dev/null | awk -v encoder="$encoder" '$2 == encoder { found = 1 } END { exit(found ? 0 : 1) }'
}
pick_webp_encoder() {
@@ -123,7 +154,7 @@ webm_vf="${crop_vf},fps=30"
echo "Generating MP4: $mp4_out"
if has_encoder "h264_nvenc"; then
echo "Trying GPU encoder for MP4: h264_nvenc"
if ffmpeg "$overwrite_flag" -i "$input" \
if "$ffmpeg_bin" "$overwrite_flag" -i "$input" \
-vf "$crop_vf" \
-c:v h264_nvenc -preset p6 -rc:v vbr -cq:v 20 -b:v 0 \
-pix_fmt yuv420p -movflags +faststart \
@@ -132,7 +163,7 @@ if has_encoder "h264_nvenc"; then
:
else
echo "GPU MP4 encode failed; retrying with CPU encoder: libx264"
ffmpeg "$overwrite_flag" -i "$input" \
"$ffmpeg_bin" "$overwrite_flag" -i "$input" \
-vf "$crop_vf" \
-c:v libx264 -preset slow -crf 20 \
-profile:v high -level 4.1 -pix_fmt yuv420p \
@@ -142,7 +173,7 @@ if has_encoder "h264_nvenc"; then
fi
else
echo "Using CPU encoder for MP4: libx264"
ffmpeg "$overwrite_flag" -i "$input" \
"$ffmpeg_bin" "$overwrite_flag" -i "$input" \
-vf "$crop_vf" \
-c:v libx264 -preset slow -crf 20 \
-profile:v high -level 4.1 -pix_fmt yuv420p \
@@ -154,7 +185,7 @@ fi
echo "Generating WebM: $webm_out"
if has_encoder "av1_nvenc"; then
echo "Trying GPU encoder for WebM: av1_nvenc"
if ffmpeg "$overwrite_flag" -i "$input" \
if "$ffmpeg_bin" "$overwrite_flag" -i "$input" \
-vf "$webm_vf" \
-c:v av1_nvenc -preset p6 -cq:v 34 -b:v 0 \
-c:a libopus -b:a 96k \
@@ -162,7 +193,7 @@ if has_encoder "av1_nvenc"; then
:
else
echo "GPU WebM encode failed; retrying with CPU encoder: libvpx-vp9"
ffmpeg "$overwrite_flag" -i "$input" \
"$ffmpeg_bin" "$overwrite_flag" -i "$input" \
-vf "$webm_vf" \
-c:v libvpx-vp9 -crf 34 -b:v 0 \
-row-mt 1 -threads 8 \
@@ -171,7 +202,7 @@ if has_encoder "av1_nvenc"; then
fi
else
echo "Using CPU encoder for WebM: libvpx-vp9"
ffmpeg "$overwrite_flag" -i "$input" \
"$ffmpeg_bin" "$overwrite_flag" -i "$input" \
-vf "$webm_vf" \
-c:v libvpx-vp9 -crf 34 -b:v 0 \
-row-mt 1 -threads 8 \
@@ -185,7 +216,7 @@ if [[ "$generate_webp" -eq 1 ]]; then
exit 1
fi
echo "Generating animated WebP with $webp_encoder: $webp_out"
ffmpeg "$overwrite_flag" -i "$input" \
"$ffmpeg_bin" "$overwrite_flag" -i "$input" \
-vf "${crop_vf},fps=24,scale=960:-1:flags=lanczos" \
-c:v "$webp_encoder" \
-q:v 80 \
@@ -195,7 +226,7 @@ if [[ "$generate_webp" -eq 1 ]]; then
fi
echo "Generating poster: $poster_out"
ffmpeg "$overwrite_flag" -ss 00:00:05 -i "$input" \
"$ffmpeg_bin" "$overwrite_flag" -ss 00:00:05 -i "$input" \
-vf "$crop_vf" \
-vframes 1 \
-q:v 2 \

View File

@@ -19,11 +19,33 @@ function writeExecutable(filePath: string, contents: string): void {
fs.chmodSync(filePath, 0o755);
}
function shellQuote(value: string): string {
return `'${value.replace(/'/g, `'\"'\"'`)}'`;
}
function toBashPath(filePath: string): string {
if (process.platform !== 'win32') return filePath;
const normalized = filePath.replace(/\\/g, '/');
const match = normalized.match(/^([A-Za-z]):\/(.*)$/);
if (!match) return normalized;
const drive = match[1]!;
const rest = match[2]!;
const probe = spawnSync('bash', ['-c', 'uname -s'], { encoding: 'utf8' });
if (probe.status === 0 && /linux/i.test(probe.stdout)) {
return `/mnt/${drive.toLowerCase()}/${rest}`;
}
return `${drive.toUpperCase()}:/${rest}`;
}
test('mkv-to-readme-video accepts libwebp_anim when libwebp is unavailable', () => {
withTempDir((root) => {
const binDir = path.join(root, 'bin');
const inputPath = path.join(root, 'sample.mkv');
const ffmpegLogPath = path.join(root, 'ffmpeg-args.log');
const ffmpegLogPathBash = toBashPath(ffmpegLogPath);
fs.mkdirSync(binDir, { recursive: true });
fs.writeFileSync(inputPath, 'fake-video', 'utf8');
@@ -44,22 +66,33 @@ EOF
exit 0
fi
printf '%s\\n' "$*" >> "${ffmpegLogPath}"
if [[ "$#" -eq 0 ]]; then
exit 0
fi
printf '%s\\n' "$*" >> "${ffmpegLogPathBash}"
output=""
for arg in "$@"; do
output="$arg"
done
if [[ -z "$output" ]]; then
exit 0
fi
mkdir -p "$(dirname "$output")"
touch "$output"
`,
);
const result = spawnSync('bash', ['scripts/mkv-to-readme-video.sh', '--webp', inputPath], {
const ffmpegShimPath = toBashPath(path.join(binDir, 'ffmpeg'));
const ffmpegShimDir = toBashPath(binDir);
const inputBashPath = toBashPath(inputPath);
const command = [
`chmod +x ${shellQuote(ffmpegShimPath)}`,
`PATH=${shellQuote(`${ffmpegShimDir}:`)}"$PATH"`,
`scripts/mkv-to-readme-video.sh --webp ${shellQuote(inputBashPath)}`,
].join('; ');
const result = spawnSync('bash', ['-lc', command], {
cwd: process.cwd(),
env: {
...process.env,
PATH: `${binDir}:${process.env.PATH || ''}`,
},
encoding: 'utf8',
});

View File

@@ -13,6 +13,26 @@ appimage=
wrapper=
assets=
normalize_path() {
local value="$1"
if command -v cygpath >/dev/null 2>&1; then
case "$value" in
[A-Za-z]:\\* | [A-Za-z]:/*)
cygpath -u "$value"
return 0
;;
esac
fi
if [[ "$value" =~ ^([A-Za-z]):[\\/](.*)$ ]]; then
local drive="${BASH_REMATCH[1],,}"
local rest="${BASH_REMATCH[2]}"
rest="${rest//\\//}"
printf '/mnt/%s/%s\n' "$drive" "$rest"
return 0
fi
printf '%s\n' "$value"
}
while [[ $# -gt 0 ]]; do
case "$1" in
--pkg-dir)
@@ -53,6 +73,10 @@ if [[ -z "$pkg_dir" || -z "$version" || -z "$appimage" || -z "$wrapper" || -z "$
fi
version="${version#v}"
pkg_dir="$(normalize_path "$pkg_dir")"
appimage="$(normalize_path "$appimage")"
wrapper="$(normalize_path "$wrapper")"
assets="$(normalize_path "$assets")"
pkgbuild="${pkg_dir}/PKGBUILD"
srcinfo="${pkg_dir}/.SRCINFO"
@@ -82,6 +106,9 @@ awk \
found_pkgver = 0
found_sha_block = 0
}
{
sub(/\r$/, "")
}
/^pkgver=/ {
print "pkgver=" version
found_pkgver = 1
@@ -140,6 +167,9 @@ awk \
found_source_wrapper = 0
found_source_assets = 0
}
{
sub(/\r$/, "")
}
/^\tpkgver = / {
print "\tpkgver = " version
found_pkgver = 1

View File

@@ -1,5 +1,6 @@
import assert from 'node:assert/strict';
import { execFileSync } from 'node:child_process';
import { execFileSync, spawnSync } from 'node:child_process';
import crypto from 'node:crypto';
import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
@@ -9,6 +10,23 @@ function createWorkspace(name: string): string {
return fs.mkdtempSync(path.join(os.tmpdir(), `${name}-`));
}
function toBashPath(filePath: string): string {
if (process.platform !== 'win32') return filePath;
const normalized = filePath.replace(/\\/g, '/');
const match = normalized.match(/^([A-Za-z]):\/(.*)$/);
if (!match) return normalized;
const drive = match[1]!;
const rest = match[2]!;
const probe = spawnSync('bash', ['-c', 'uname -s'], { encoding: 'utf8' });
if (probe.status === 0 && /linux/i.test(probe.stdout)) {
return `/mnt/${drive.toLowerCase()}/${rest}`;
}
return `${drive.toUpperCase()}:/${rest}`;
}
test('update-aur-package updates PKGBUILD and .SRCINFO without makepkg', () => {
const workspace = createWorkspace('subminer-aur-package');
const pkgDir = path.join(workspace, 'aur-subminer-bin');
@@ -29,15 +47,15 @@ test('update-aur-package updates PKGBUILD and .SRCINFO without makepkg', () => {
[
'scripts/update-aur-package.sh',
'--pkg-dir',
pkgDir,
toBashPath(pkgDir),
'--version',
'v0.6.3',
'--appimage',
appImagePath,
toBashPath(appImagePath),
'--wrapper',
wrapperPath,
toBashPath(wrapperPath),
'--assets',
assetsPath,
toBashPath(assetsPath),
],
{
cwd: process.cwd(),
@@ -47,8 +65,8 @@ test('update-aur-package updates PKGBUILD and .SRCINFO without makepkg', () => {
const pkgbuild = fs.readFileSync(path.join(pkgDir, 'PKGBUILD'), 'utf8');
const srcinfo = fs.readFileSync(path.join(pkgDir, '.SRCINFO'), 'utf8');
const expectedSums = [appImagePath, wrapperPath, assetsPath].map(
(filePath) => execFileSync('sha256sum', [filePath], { encoding: 'utf8' }).split(/\s+/)[0],
const expectedSums = [appImagePath, wrapperPath, assetsPath].map((filePath) =>
crypto.createHash('sha256').update(fs.readFileSync(filePath)).digest('hex'),
);
assert.match(pkgbuild, /^pkgver=0\.6\.3$/m);