Separate per-kind video/audio folders, defaulting to Documents\Video and Documents\Audio

Existing installs had outputDir persisted to the Downloads folder (auto-filled by
earlier versions), so the v0.3.0 per-kind routing never triggered — downloads kept
going to Downloads. Replace the single outputDir setting with independent videoDir
and audioDir settings:

- Settings model: drop Settings.outputDir; add videoDir + audioDir (both blank by
  default). A blank value routes that kind into Documents\Video / Documents\Audio;
  a chosen folder overrides it. The stale outputDir key in old settings files is
  simply ignored, so existing users now get the Documents defaults.
- buildCommand routes by kind: per-download override → per-kind folder → default.
- The renderer no longer sends a global outputDir, so main always routes by kind.
- Settings: the single "Download folder" field becomes separate "Video folder" and
  "Audio folder" pickers, each with a Browse + Reset and a Documents\… placeholder.
  Store gains chooseDir(target) / clearDir(target).
- Onboarding: replace the folder picker with a short note about the two default
  folders (changeable in Settings).
- Bump version to 0.3.1.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-24 06:11:15 -04:00
parent a2763f10b4
commit 37687f9870
9 changed files with 85 additions and 56 deletions
+4 -4
View File
@@ -166,10 +166,10 @@ function resolveExtraArgs(opts: StartDownloadOptions, settings: Settings): strin
export function buildCommand(opts: StartDownloadOptions): string[] {
const settings = getSettings()
// Output dir resolution: a per-download override wins, then the user's explicit
// global folder (Settings → Download folder), and finally — when both are blank —
// the per-kind default (Documents\Video for video, Documents\Audio for audio).
const outDir =
opts.outputDir?.trim() || settings.outputDir?.trim() || getDefaultMediaDir(opts.kind)
// per-kind folder (Settings → Video/Audio folder), and finally — when that's
// blank — the per-kind default (Documents\Video for video, Documents\Audio for audio).
const perKindDir = (opts.kind === 'audio' ? settings.audioDir : settings.videoDir)?.trim()
const outDir = opts.outputDir?.trim() || perKindDir || getDefaultMediaDir(opts.kind)
// A collection (media-manager) download is filed into <channel>/<playlist>/
// <NNN> - <title> folders; an ordinary download uses the flat filenameTemplate.
const filenameTemplate = settings.filenameTemplate?.trim() || '%(title)s.%(ext)s'
+14 -6
View File
@@ -17,7 +17,10 @@ import {
} from '@shared/ipc'
const DEFAULTS: Settings = {
outputDir: '', // blank = route by kind into Documents\Video / Documents\Audio (see getDefaultMediaDir)
// Both blank by default → downloads land in Documents\Video / Documents\Audio
// (see getDefaultMediaDir). A non-empty value is an explicit per-kind override.
videoDir: '',
audioDir: '',
defaultKind: 'video',
defaultVideoQuality: 'Best available',
defaultAudioQuality: 'Best (MP3)',
@@ -126,9 +129,9 @@ export function getSettings(): Settings {
// `set`, so only write when something actually changed — otherwise this churns
// the settings file on every read. (audit P1)
const cur = s.store
// outputDir is intentionally left blank by default — an empty value routes each
// download into Documents\Video / Documents\Audio by kind (see buildCommand).
// Only an explicit user choice (Settings → Download folder) overrides that.
// videoDir/audioDir are intentionally left blank by default — an empty value
// routes that kind into Documents\Video / Documents\Audio (see buildCommand).
// Only an explicit user choice (Settings → folders) overrides that.
// Migrate settings files that predate downloadOptions (or hold a partial one),
// but only persist when sanitizing actually altered the stored value.
const sanitized = sanitizeOptions(cur.downloadOptions)
@@ -212,8 +215,13 @@ export function setSettings(partial: Partial<Settings>): Settings {
// group (it merges field changes locally before calling setSettings).
s.set('downloadOptions', sanitizeOptions(value))
break
case 'outputDir':
if (typeof value === 'string' && isSafeOutputDir(value.trim())) s.set('outputDir', value.trim())
case 'videoDir':
case 'audioDir':
// An empty string is allowed — it clears the override and restores the
// Documents\Video / Documents\Audio default for that kind.
if (typeof value === 'string' && (value.trim() === '' || isSafeOutputDir(value.trim()))) {
s.set(key, value.trim())
}
break
case 'filenameTemplate':
if (typeof value === 'string' && isSafeFilenameTemplate(value.trim())) {