Compare commits

..

No commits in common. "a07ec1f958f009f59deb7c8edbcd75b735be638f" and "39fa5065d23348b55b8aec51aa644d05e50dda79" have entirely different histories.

4 changed files with 29 additions and 82 deletions

4
package-lock.json generated
View File

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

View File

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

View File

@ -269,16 +269,8 @@ function loadConfig(): Config {
} }
function saveConfig(config: Config): void { function saveConfig(config: Config): void {
const tmpPath = CONFIG_FILE + '.tmp';
try { try {
fs.writeFileSync(tmpPath, JSON.stringify(config, null, 2)); fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
try {
fs.renameSync(tmpPath, CONFIG_FILE);
} catch {
// On Windows, rename can fail if target exists in some edge cases
fs.copyFileSync(tmpPath, CONFIG_FILE);
try { fs.unlinkSync(tmpPath); } catch { }
}
} catch (e) { } catch (e) {
console.error('Error saving config:', e); console.error('Error saving config:', e);
} }
@ -329,16 +321,8 @@ function writeQueueToDisk(queue: QueueItem[]): void {
return; return;
} }
const tmpPath = QUEUE_FILE + '.tmp';
try { try {
fs.writeFileSync(tmpPath, JSON.stringify(queue, null, 2)); fs.writeFileSync(QUEUE_FILE, JSON.stringify(queue, null, 2));
try {
fs.renameSync(tmpPath, QUEUE_FILE);
} catch {
// On Windows, rename can fail if target exists in some edge cases
fs.copyFileSync(tmpPath, QUEUE_FILE);
try { fs.unlinkSync(tmpPath); } catch { }
}
} catch (e) { } catch (e) {
console.error('Error saving queue:', e); console.error('Error saving queue:', e);
} }
@ -1649,18 +1633,13 @@ async function getVODs(userId: string, forceRefresh = false): Promise<VOD[]> {
if (!(await ensureTwitchAuth())) return await getVodsViaPublicApi(); if (!(await ensureTwitchAuth())) return await getVodsViaPublicApi();
const MAX_VOD_PAGES = 50; // 50 pages x 100 per page = 5000 VODs max const fetchVods = async () => {
return await axios.get('https://api.twitch.tv/helix/videos', {
const fetchVodsPage = async (cursor?: string) => { params: {
const params: Record<string, string | number> = {
user_id: userId, user_id: userId,
type: 'archive', type: 'archive',
first: 100 first: 100
}; },
if (cursor) params.after = cursor;
return await axios.get('https://api.twitch.tv/helix/videos', {
params,
headers: { headers: {
'Client-ID': config.client_id, 'Client-ID': config.client_id,
'Authorization': `Bearer ${accessToken}` 'Authorization': `Bearer ${accessToken}`
@ -1669,38 +1648,26 @@ async function getVODs(userId: string, forceRefresh = false): Promise<VOD[]> {
}); });
}; };
const fetchAllVodPages = async (): Promise<VOD[]> => { try {
const allVods: VOD[] = []; const response = await fetchVods();
let cursor: string | undefined; const vods = response.data.data || [];
let pageCount = 0; const login = vods[0]?.user_login;
do {
const response = await fetchVodsPage(cursor);
const pageVods = response.data.data || [];
allVods.push(...pageVods);
if (pageCount === 0) {
const login = pageVods[0]?.user_login;
if (login) { if (login) {
userIdLoginCache.set(userId, normalizeLogin(login)); userIdLoginCache.set(userId, normalizeLogin(login));
} }
}
cursor = response.data.pagination?.cursor;
pageCount++;
} while (cursor && pageCount < MAX_VOD_PAGES);
return allVods;
};
try {
const vods = await fetchAllVodPages();
setCachedValue(vodListCache, cacheKey, vods, MAX_VOD_LIST_CACHE_ENTRIES); setCachedValue(vodListCache, cacheKey, vods, MAX_VOD_LIST_CACHE_ENTRIES);
return vods; return vods;
} catch (e) { } catch (e) {
if (axios.isAxiosError(e) && e.response?.status === 401 && (await ensureTwitchAuth(true))) { if (axios.isAxiosError(e) && e.response?.status === 401 && (await ensureTwitchAuth(true))) {
try { try {
const vods = await fetchAllVodPages(); const retryResponse = await fetchVods();
const vods = retryResponse.data.data || [];
const login = vods[0]?.user_login;
if (login) {
userIdLoginCache.set(userId, normalizeLogin(login));
}
setCachedValue(vodListCache, cacheKey, vods, MAX_VOD_LIST_CACHE_ENTRIES); setCachedValue(vodListCache, cacheKey, vods, MAX_VOD_LIST_CACHE_ENTRIES);
return vods; return vods;
} catch (retryError) { } catch (retryError) {
@ -1939,17 +1906,7 @@ async function cutVideo(
proc.on('close', (code) => { proc.on('close', (code) => {
currentProcess = null; currentProcess = null;
if (code === 0 && fs.existsSync(outputFile)) { resolve(code === 0 && fs.existsSync(outputFile));
const stats = fs.statSync(outputFile);
if (stats.size <= 256) {
appendDebugLog('cut-video-empty-output', { outputFile, bytes: stats.size });
resolve(false);
return;
}
resolve(true);
} else {
resolve(false);
}
}); });
proc.on('error', () => { proc.on('error', () => {

View File

@ -805,28 +805,17 @@ async function confirmClipDialog(): Promise<void> {
const startSec = parseTimeToSeconds(byId<HTMLInputElement>('clipStartTime').value); const startSec = parseTimeToSeconds(byId<HTMLInputElement>('clipStartTime').value);
const endSec = parseTimeToSeconds(byId<HTMLInputElement>('clipEndTime').value); const endSec = parseTimeToSeconds(byId<HTMLInputElement>('clipEndTime').value);
const durationSec = endSec - startSec;
const startPartStr = byId<HTMLInputElement>('clipStartPart').value.trim(); const startPartStr = byId<HTMLInputElement>('clipStartPart').value.trim();
const startPart = startPartStr ? parseInt(startPartStr, 10) : 1; const startPart = startPartStr ? parseInt(startPartStr, 10) : 1;
const filenameFormat = getSelectedFilenameFormat(); const filenameFormat = getSelectedFilenameFormat();
const filenameTemplate = byId<HTMLInputElement>('clipFilenameTemplate').value.trim(); const filenameTemplate = byId<HTMLInputElement>('clipFilenameTemplate').value.trim();
if (isNaN(startSec) || isNaN(endSec) || isNaN(durationSec)) { if (endSec <= startSec) {
alert('Invalid time values');
return;
}
if (startSec < 0) {
alert(UI_TEXT.clips.outOfRange);
return;
}
if (durationSec <= 0) {
alert(UI_TEXT.clips.endBeforeStart); alert(UI_TEXT.clips.endBeforeStart);
return; return;
} }
if (endSec > clipTotalSeconds) { if (startSec < 0 || endSec > clipTotalSeconds) {
alert(UI_TEXT.clips.outOfRange); alert(UI_TEXT.clips.outOfRange);
return; return;
} }
@ -844,6 +833,7 @@ async function confirmClipDialog(): Promise<void> {
} }
} }
const durationSec = endSec - startSec;
const customClip: CustomClip = { const customClip: CustomClip = {
startSec, startSec,
durationSec, durationSec,