feat(pinchflat): Media Profiles — named download presets per Library source
A MediaProfile bundles { kind, quality, options, outputTemplate, extraArgs }
that a Source points at via Source.profileId, overriding the global defaults
field-by-field for that source's downloads.
Backend mirrors CommandTemplate: profiles.ts (createJsonStore, PROFILES_MAX,
isProfileLike + sanitize), CRUD IPC (profiles:list/save/remove) + preload +
useProfiles store; Source.profileId + sources:set-profile. Pure resolution
in @shared/profileResolve.ts (profile → global, per field; unit-tested).
Applied in the sources-store enqueueItems; a per-download collectionOutput-
Template override threads to buildArgs so a profile's outputTemplate wins.
UI: ProfileManager (mirrors TemplateManager) in a new Settings → Advanced →
Media profiles card, plus a per-source Profile picker in the Library source
detail. Both live-verified in the preview; 354 tests pass.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -42,6 +42,10 @@ export const IpcChannels = {
|
||||
templatesList: 'templates:list',
|
||||
templatesSave: 'templates:save',
|
||||
templatesRemove: 'templates:remove',
|
||||
/** Media profiles: named download presets a Source can point at (PINCHFLAT) */
|
||||
profilesList: 'profiles:list',
|
||||
profilesSave: 'profiles:save',
|
||||
profilesRemove: 'profiles:remove',
|
||||
commandPreview: 'command:preview',
|
||||
ytdlpUpdate: 'ytdlp:update',
|
||||
/** main → renderer push channel for background yt-dlp auto-update status */
|
||||
@@ -78,6 +82,8 @@ export const IpcChannels = {
|
||||
sourceItemDownloaded: 'sources:item-downloaded',
|
||||
/** toggle whether a source is watched for new uploads (Phase J) */
|
||||
sourceSetWatched: 'sources:set-watched',
|
||||
/** point a source at a MediaProfile (or clear it) */
|
||||
sourceSetProfile: 'sources:set-profile',
|
||||
/** re-index all watched sources and return the videos that are new */
|
||||
sourcesSync: 'sources:sync',
|
||||
/** get / set the Windows scheduled-sync task (Task Scheduler) */
|
||||
@@ -476,6 +482,12 @@ export interface StartDownloadOptions {
|
||||
* filenameTemplate. See CollectionContext.
|
||||
*/
|
||||
collection?: CollectionContext
|
||||
/**
|
||||
* Per-download collection folder-layout override (a Media Profile's
|
||||
* `outputTemplate`). When set, it overrides `Settings.collectionOutputTemplate`
|
||||
* for this collection download; omit to fall back to the global setting.
|
||||
*/
|
||||
collectionOutputTemplate?: string
|
||||
}
|
||||
|
||||
export interface StartDownloadResult {
|
||||
@@ -847,6 +859,31 @@ export interface Source {
|
||||
watched?: boolean
|
||||
/** YouTube RSS feed URL for cheap "anything new?" checks; undefined if unknown */
|
||||
feedUrl?: string
|
||||
/** id of the MediaProfile applied to this source's downloads; undefined = global defaults */
|
||||
profileId?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* A named download preset a Library Source can point at (PINCHFLAT "Media
|
||||
* Profile"): a reusable bundle of the per-download choices that would otherwise
|
||||
* come from the global defaults — kind, quality, post-processing options, the
|
||||
* collection folder layout, and extra yt-dlp args. A Source with a `profileId`
|
||||
* resolves those fields from the profile first, then falls back to the global
|
||||
* settings for any field the profile leaves unset (see resolveProfile).
|
||||
*/
|
||||
export interface MediaProfile {
|
||||
id: string
|
||||
name: string
|
||||
/** override the download kind (video/audio); unset = global default */
|
||||
kind?: MediaKind
|
||||
/** override the quality label ('1080p', 'Best', …); unset = global default */
|
||||
quality?: string
|
||||
/** override post-processing options; unset = global default */
|
||||
options?: DownloadOptions
|
||||
/** override the collection folder layout (raw yt-dlp -o template); unset = global default */
|
||||
outputTemplate?: string
|
||||
/** extra yt-dlp args appended to the argv (like a CommandTemplate's args); unset = none */
|
||||
extraArgs?: string
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
import type { MediaProfile, MediaKind, DownloadOptions } from './ipc'
|
||||
|
||||
/**
|
||||
* The global defaults a Media Profile can override — exactly the fields a
|
||||
* collection download would otherwise pull from Settings.
|
||||
*/
|
||||
export interface ProfileDefaults {
|
||||
kind: MediaKind
|
||||
videoQuality: string
|
||||
audioQuality: string
|
||||
options: DownloadOptions
|
||||
/** Settings.collectionOutputTemplate — the global collection folder layout */
|
||||
outputTemplate: string
|
||||
}
|
||||
|
||||
/** The resolved per-download values a Source's profile (or the globals) produce. */
|
||||
export interface ResolvedProfile {
|
||||
kind: MediaKind
|
||||
quality: string
|
||||
options: DownloadOptions
|
||||
/** collection folder layout ('' = the built-in default), or the profile override */
|
||||
outputTemplate: string
|
||||
/** extra yt-dlp args from the profile ('' when none) */
|
||||
extraArgs: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve a download's effective settings for a Library source: **profile →
|
||||
* global default**, field by field. A `null`/`undefined` profile (source not
|
||||
* pointed at one, or a dangling id) yields the pure global defaults. The quality
|
||||
* default follows the resolved kind, so an audio-kind profile without its own
|
||||
* quality picks up the global AUDIO default (not video).
|
||||
*
|
||||
* Lives in `@shared` because both the renderer's enqueue path (which needs the
|
||||
* resolved kind/quality for the queued item) and any main-side use share this
|
||||
* one precedence rule. Pure — unit-tested in test/profileResolve.test.ts.
|
||||
*/
|
||||
export function resolveProfile(
|
||||
profile: MediaProfile | null | undefined,
|
||||
defaults: ProfileDefaults
|
||||
): ResolvedProfile {
|
||||
const kind = profile?.kind ?? defaults.kind
|
||||
const globalQuality = kind === 'audio' ? defaults.audioQuality : defaults.videoQuality
|
||||
return {
|
||||
kind,
|
||||
quality: profile?.quality ?? globalQuality,
|
||||
options: profile?.options ?? defaults.options,
|
||||
outputTemplate: profile?.outputTemplate ?? defaults.outputTemplate,
|
||||
extraArgs: profile?.extraArgs ?? ''
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user