Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,46 @@ This is the first documented entry — a snapshot of what SlothArchiver could
do as of this release, not a history of every change that got it here.
Future releases will log what actually changed from the previous one.

## [1.1.0] — 2026-09-11
- **Player improvements.**
- **Resume where you left off.** Reopening a video you didn't finish now
offers to pick up right where you paused, via a dismissible toast. A
new Options setting controls when a position gets remembered at all —
never, always, or only past a minimum length you choose (20 minutes by
default), so a fresh install doesn't start tracking every short clip.
- **Loop a video, or loop just a clipped sequence.** Right-click the
player for a new context menu (with room for more playback options
down the line) offering "Loop" for the whole video and "Loop
sequence" to loop only between your clip markers, once both are set.
- **Clip markers and extraction are now millisecond-precise**, not
rounded to the nearest whole second — dragging a clip marker or
setting one from the current playback position keeps the exact
fraction of a second you picked, instead of losing it to rounding.
Also fixed a bug that could cause an unnecessary, slower re-encode
when clipping an audio-only file.
- **Double-click the time display for a millisecond-precision readout**, useful alongside the new fine-grained clip markers.
Double-click again to go back to the normal display.
- **Downloads embed their metadata by default now.** Title, channel, upload
date, description, and cover art get written into the file automatically
on every download — library and the plain Downloader tab alike. This was
previously a manual "Embed metadata" button; it's now on by default and
can be turned off in Options.
- **Tag playlist entries, and filter playlists by tag** the same way the
library already lets you — including bulk-tagging several entries at
once, and new "Downloaded"/"Not Downloaded" filters available in both the
library and playlist tag filters.
- **"Select all" for whatever's currently on screen** — the flat video
list, a channel's video grid, and a playlist's entry list all gained a
header checkbox that selects (or clears) exactly the currently
filtered/searched set, for use with the existing bulk actions.
- **Fixed a bug where an MP3 download failure showed no error at all** in
the video view, silently reverting to the plain "Download MP3" button
instead of telling you something went wrong.
- **The sandboxed JS runtime yt-dlp uses to solve site challenges is now Deno instead of Node** closing a gap where the previous approach could
not fully block that runtime's network and environment-variable access.
No user-facing change in behavior — this is a security hardening
improvement under the hood.

## [1.0.0] — 2026-09-05
- **First public release** 🎉🎉🥳🎉🎉
- **Added a theme selector.** Options now has a "Theme" dropdown alongside
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,17 +28,17 @@ itself.

## Download

<!-- Update the version in these 5 links on every release, currently 1.0.0.
<!-- Update the version in these 5 links on every release, currently 1.1.0.
Each filename must match exactly what GitHub actually named the asset on
the release page -->

| Platform | Link |
|---|---|
| Windows (installer) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.0.0/SlothArchiver.Setup.1.0.0.exe) |
| Windows (portable, no install) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.0.0/SlothArchiver.1.0.0.exe) |
| macOS (Apple Silicon) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.0.0/SlothArchiver-1.0.0-arm64.dmg) |
| macOS (Intel) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.0.0/SlothArchiver-1.0.0.dmg) |
| Linux (AppImage) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.0.0/SlothArchiver-1.0.0.AppImage) |
| Windows (installer) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.1.0/SlothArchiver.Setup.1.1.0.exe) |
| Windows (portable, no install) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.1.0/SlothArchiver.1.1.0.exe) |
| macOS (Apple Silicon) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.1.0/SlothArchiver-1.1.0-arm64.dmg) |
| macOS (Intel) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.1.0/SlothArchiver-1.1.0.dmg) |
| Linux (AppImage) | [Download](https://github.com/SlothSoftworks/Sloth-Archiver/releases/download/v1.1.0/SlothArchiver-1.1.0.AppImage) |

All builds are unsigned, so your OS will show a first-run security warning.
See [Installing](#installing) below.
Expand Down
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

12 changes: 10 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "sloth-archiver",
"private": true,
"version": "1.0.0",
"version": "1.1.0",
"description": "A desktop app for downloading and archiving videos, with a built-in library and playlist tracking.",
"author": "Sloth <sloth.software.dev@protonmail.com>",
"license": "GPL-3.0-or-later",
Expand Down Expand Up @@ -29,6 +29,13 @@
"filter": [
"**/*"
]
},
{
"from": "dist/deno/",
"to": "deno",
"filter": [
"**/*"
]
}
],
"directories": {
Expand Down Expand Up @@ -66,7 +73,8 @@
"build:copy:electron": "node scripts/copy-electron.mjs",
"build:ytdlp:bin": "node scripts/fetch-ytdlp-bin.mjs",
"build:copy:ffmpeg": "node scripts/copy-ffmpeg.mjs",
"build:electron": "npm run build:copy:electron && npm run build:ytdlp:bin && npm run build:copy:ffmpeg",
"build:deno:bin": "node scripts/fetch-deno-bin.mjs",
"build:electron": "npm run build:copy:electron && npm run build:ytdlp:bin && npm run build:copy:ffmpeg && npm run build:deno:bin",
"build:all": "npm run build:renderer && npm run build:electron",
"test": "vitest run",
"test:watch": "vitest",
Expand Down
67 changes: 67 additions & 0 deletions scripts/fetch-deno-bin.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import fs from 'fs';
import path from 'path';
import { execFileSync } from 'child_process';
import { fileURLToPath } from 'url';
import { resolveLatestRelease, mapPlatformToAssetName, fetchAndVerifyDenoRelease } from '../src/electron/denoRelease.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(__dirname, '..');
const workDir = path.join(rootDir, 'build', 'deno-fetch');
const finalDist = path.join(rootDir, 'dist', 'deno');
const binName = process.platform === 'win32' ? 'deno.exe' : 'deno';

// Pinned so every SlothArchiver release bundles a known, reproducible Deno
// version rather than "whatever GitHub's latest release happened to be at
// build time" -- same reasoning as fetch-ytdlp-bin.mjs's own
// PINNED_YTDLP_TAG. This exact version (v2.9.5) was validated end-to-end
// this session against a real yt-dlp nsig solve. Bump by hand in the same
// commit as any other dependency-version bump.
const PINNED_DENO_TAG = 'v2.9.5';

// Same repeat-local-build shortcut as fetch-ytdlp-bin.mjs -- skips the
// download+verify+extract round trip when the already-installed binary
// already reports the pinned version. `deno --version`'s first line is
// `deno <version>` with no "v" prefix (unlike the release tag, which has
// one) -- add it back before comparing. Falls through to a full re-fetch on
// any uncertainty (missing binary, wrong version, unparseable output, or a
// binary that fails to even run).
const existingBinaryPath = path.join(finalDist, binName);
if (fs.existsSync(existingBinaryPath)) {
try {
const versionOutput = execFileSync(existingBinaryPath, ['--version'], { encoding: 'utf-8' });
const match = /^deno (\S+)/.exec(versionOutput);
const currentVersion = match ? `v${match[1]}` : null;
if (currentVersion === PINNED_DENO_TAG) {
console.log(`Deno ${PINNED_DENO_TAG} already installed at ${existingBinaryPath} -- skipping fetch.`);
process.exit(0);
}
console.log(`Installed Deno is ${currentVersion ?? '(unparseable)'}, pinned is ${PINNED_DENO_TAG} -- re-fetching.`);
} catch {
console.log(`Existing Deno binary at ${existingBinaryPath} could not report its version -- re-fetching.`);
}
}

fs.rmSync(workDir, { recursive: true, force: true });
fs.rmSync(finalDist, { recursive: true, force: true });

const release = await resolveLatestRelease({ pin: PINNED_DENO_TAG });
const assetName = mapPlatformToAssetName();

console.log(`Fetching Deno ${release.tag} (${assetName})...`);
const extractedDir = await fetchAndVerifyDenoRelease({
release,
assetName,
workDir,
onProgress: (stage) => console.log(`[fetch-deno-bin] ${stage}`),
onLog: (msg) => console.log(msg),
});

// fetchAndVerifyDenoRelease's ZIP extraction lands the single `deno`/
// `deno.exe` binary directly in extractedDir, with no wrapper folder --
// confirmed against the real macOS asset -- so this is just a rename into
// place, matching the final shape package.json's `dist/deno/`
// extraResources entry expects.
fs.renameSync(extractedDir, finalDist);
fs.rmSync(workDir, { recursive: true, force: true });

console.log(`Fetched Deno ${release.tag} -> ${path.join(finalDist, binName)}`);
25 changes: 24 additions & 1 deletion scripts/fetch-ytdlp-bin.mjs
Original file line number Diff line number Diff line change
@@ -1,19 +1,43 @@
import fs from 'fs';
import path from 'path';
import { execFileSync } from 'child_process';
import { fileURLToPath } from 'url';
import { resolveLatestRelease, mapPlatformToAssetName, detectMusl, fetchAndVerifyRelease } from '../src/electron/ytdlpRelease.mjs';

const __dirname = path.dirname(fileURLToPath(import.meta.url));
const rootDir = path.resolve(__dirname, '..');
const workDir = path.join(rootDir, 'build', 'ytdlp-fetch');
const finalDist = path.join(rootDir, 'dist', 'ytdlp-bin');
const binName = process.platform === 'win32' ? 'yt-dlp.exe' : 'yt-dlp';

// Pinned so every SlothArchiver release bundles a known, reproducible
// yt-dlp version rather than "whatever GitHub's latest release happened to
// be at build time." Bump this by hand in the same commit as any other
// dependency-version bump.
const PINNED_YTDLP_TAG = '2026.08.19';

// Skips the whole download+verify+extract round trip on a repeat local
// build when the already-installed binary already reports the exact pinned
// version -- this is the slow part of `npm run build:electron` (a
// multi-dozen-MB network fetch plus signature verification) and the pinned
// tag rarely changes between builds. Falls through to a full re-fetch on
// *any* uncertainty (missing binary, wrong version, or the binary failing
// to even report its own version) rather than trusting a stale/corrupt
// install.
const existingBinaryPath = path.join(finalDist, binName);
if (fs.existsSync(existingBinaryPath)) {
try {
const currentVersion = execFileSync(existingBinaryPath, ['--version'], { encoding: 'utf-8' }).trim();
if (currentVersion === PINNED_YTDLP_TAG) {
console.log(`yt-dlp ${PINNED_YTDLP_TAG} already installed at ${existingBinaryPath} -- skipping fetch.`);
process.exit(0);
}
console.log(`Installed yt-dlp is ${currentVersion}, pinned is ${PINNED_YTDLP_TAG} -- re-fetching.`);
} catch {
console.log(`Existing yt-dlp binary at ${existingBinaryPath} could not report its version -- re-fetching.`);
}
}

fs.rmSync(workDir, { recursive: true, force: true });
fs.rmSync(finalDist, { recursive: true, force: true });

Expand All @@ -38,5 +62,4 @@ const extractedDir = await fetchAndVerifyRelease({
fs.renameSync(extractedDir, finalDist);
fs.rmSync(workDir, { recursive: true, force: true });

const binName = process.platform === 'win32' ? 'yt-dlp.exe' : 'yt-dlp';
console.log(`Fetched yt-dlp ${release.tag} -> ${path.join(finalDist, binName)}`);
Loading
Loading