Add streamlink command fallback and improve ENOENT errors (v3.7.9)

Resolve streamlink execution more robustly by trying direct binary and Python module launchers, then return actionable error messages when streamlink is missing on server environments.
This commit is contained in:
xRangerDE 2026-02-13 12:14:42 +01:00
parent 78378b9812
commit a3c3c6d225
4 changed files with 64 additions and 13 deletions

View File

@ -1,12 +1,12 @@
{
"name": "twitch-vod-manager",
"version": "3.7.8",
"version": "3.7.9",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "twitch-vod-manager",
"version": "3.7.8",
"version": "3.7.9",
"license": "MIT",
"dependencies": {
"axios": "^1.6.0",

View File

@ -1,6 +1,6 @@
{
"name": "twitch-vod-manager",
"version": "3.7.8",
"version": "3.7.9",
"description": "Twitch VOD Manager - Download Twitch VODs easily",
"main": "dist/main.js",
"author": "xRangerDE",

View File

@ -335,7 +335,7 @@
<div class="settings-card">
<h3>Updates</h3>
<p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v3.7.8</p>
<p id="versionInfo" style="margin-bottom: 10px; color: var(--text-secondary);">Version: v3.7.9</p>
<button class="btn-secondary" onclick="checkUpdate()">Nach Updates suchen</button>
</div>
</div>
@ -346,7 +346,7 @@
<div class="status-dot" id="statusDot"></div>
<span id="statusText">Nicht verbunden</span>
</div>
<span id="versionText">v3.7.8</span>
<span id="versionText">v3.7.9</span>
</div>
</main>
</div>

View File

@ -8,7 +8,7 @@ import { autoUpdater } from 'electron-updater';
// ==========================================
// CONFIG & CONSTANTS
// ==========================================
const APP_VERSION = '3.7.8';
const APP_VERSION = '3.7.9';
const UPDATE_CHECK_URL = 'http://24-music.de/version.json';
// Paths
@ -172,6 +172,7 @@ let currentDownloadCancelled = false;
let downloadStartTime = 0;
let downloadedBytes = 0;
const userIdLoginCache = new Map<string, string>();
let streamlinkCommandCache: { command: string; prefixArgs: string[] } | null = null;
// ==========================================
// TOOL PATHS
@ -201,6 +202,52 @@ function getStreamlinkPath(): string {
return 'streamlink';
}
function canExecute(cmd: string): boolean {
try {
execSync(cmd, { stdio: 'ignore', windowsHide: true });
return true;
} catch {
return false;
}
}
function getStreamlinkCommand(): { command: string; prefixArgs: string[] } {
if (streamlinkCommandCache) {
return streamlinkCommandCache;
}
const directPath = getStreamlinkPath();
if (directPath !== 'streamlink' || canExecute('streamlink --version')) {
streamlinkCommandCache = { command: directPath, prefixArgs: [] };
return streamlinkCommandCache;
}
if (process.platform === 'win32') {
if (canExecute('py -3 -m streamlink --version')) {
streamlinkCommandCache = { command: 'py', prefixArgs: ['-3', '-m', 'streamlink'] };
return streamlinkCommandCache;
}
if (canExecute('python -m streamlink --version')) {
streamlinkCommandCache = { command: 'python', prefixArgs: ['-m', 'streamlink'] };
return streamlinkCommandCache;
}
} else {
if (canExecute('python3 -m streamlink --version')) {
streamlinkCommandCache = { command: 'python3', prefixArgs: ['-m', 'streamlink'] };
return streamlinkCommandCache;
}
if (canExecute('python -m streamlink --version')) {
streamlinkCommandCache = { command: 'python', prefixArgs: ['-m', 'streamlink'] };
return streamlinkCommandCache;
}
}
streamlinkCommandCache = { command: directPath, prefixArgs: [] };
return streamlinkCommandCache;
}
function getFFmpegPath(): string {
try {
if (process.platform === 'win32') {
@ -777,8 +824,8 @@ function downloadVODPart(
totalParts: number
): Promise<DownloadResult> {
return new Promise((resolve) => {
const streamlinkPath = getStreamlinkPath();
const args = [url, 'best', '-o', filename, '--force'];
const streamlinkCmd = getStreamlinkCommand();
const args = [...streamlinkCmd.prefixArgs, url, 'best', '-o', filename, '--force'];
let lastErrorLine = '';
if (startTime) {
@ -788,10 +835,10 @@ function downloadVODPart(
args.push('--hls-duration', endTime);
}
console.log('Starting download:', streamlinkPath, args);
appendDebugLog('download-part-start', { itemId, streamlinkPath, filename, args });
console.log('Starting download:', streamlinkCmd.command, args);
appendDebugLog('download-part-start', { itemId, command: streamlinkCmd.command, filename, args });
const proc = spawn(streamlinkPath, args, { windowsHide: true });
const proc = spawn(streamlinkCmd.command, args, { windowsHide: true });
currentProcess = proc;
downloadStartTime = Date.now();
@ -890,8 +937,12 @@ function downloadVODPart(
clearInterval(progressInterval);
console.error('Process error:', err);
currentProcess = null;
appendDebugLog('download-part-process-error', { itemId, error: String(err) });
resolve({ success: false, error: String(err) });
const rawError = String(err);
const errorMessage = rawError.includes('ENOENT')
? 'Streamlink nicht gefunden. Installiere Streamlink oder Python+streamlink (py -3 -m pip install streamlink).'
: rawError;
appendDebugLog('download-part-process-error', { itemId, error: errorMessage, rawError });
resolve({ success: false, error: errorMessage });
});
});
}