Add Seal-parity download options + playlist support (Phase A)
Phase A of the Seal feature-parity roadmap: configurable post-processing via a shared DownloadOptions model (src/shared/ipc.ts), editable as persisted defaults in Settings and overridable per-download in the download bar. - audio formats (mp3/m4a/opus/flac/wav/aac), video container (mp4/mkv/webm), preferred-codec sort (-S vcodec) - subtitles (download/embed/langs/auto), SponsorBlock (remove/mark + categories + force-keyframes-at-cuts), embed chapters/metadata/thumbnail, square-crop audio artwork - playlist downloads: probe via `-J --flat-playlist`, inline selection UI (checkbox list, select-all/none, live count); each entry enqueued as its own single-video download, reusing the existing queue/concurrency/history - reusable DownloadOptionsForm shared by Settings and the download bar so the defaults and per-download override never drift - ROADMAP.md tracking full Seal parity (phases A-E) Also includes in-tree security hardening that landed alongside: preload sandbox (CJS preload output), http(s)-only window-open + blocked renderer navigation, argv-injection guard for URLs (src/main/url.ts), extension-allowlisted file open/reveal (src/main/reveal.ts), yt-dlp version-check timeout, and a minimal window.electron presence marker. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,84 @@ export const BEST_FORMAT_ID = '__best__'
|
||||
|
||||
export type MediaKind = 'video' | 'audio'
|
||||
|
||||
// --- Post-processing / format options ---------------------------------------
|
||||
|
||||
/** Audio extraction target. 'best' keeps the source codec without re-encoding. */
|
||||
export type AudioFormat = 'best' | 'mp3' | 'm4a' | 'opus' | 'flac' | 'wav' | 'aac'
|
||||
export const AUDIO_FORMATS: AudioFormat[] = ['best', 'mp3', 'm4a', 'opus', 'flac', 'wav', 'aac']
|
||||
|
||||
/** Container the merged video is muxed into (`--merge-output-format`). */
|
||||
export type VideoContainer = 'mp4' | 'mkv' | 'webm'
|
||||
export const VIDEO_CONTAINERS: VideoContainer[] = ['mp4', 'mkv', 'webm']
|
||||
|
||||
/** Preferred video codec, applied as a `-S vcodec:…` sort tiebreaker. */
|
||||
export type VideoCodecPref = 'any' | 'h264' | 'vp9' | 'av1'
|
||||
export const VIDEO_CODECS: VideoCodecPref[] = ['any', 'h264', 'vp9', 'av1']
|
||||
|
||||
/** Whether SponsorBlock cuts the segments out or just marks them as chapters. */
|
||||
export type SponsorBlockMode = 'remove' | 'mark'
|
||||
|
||||
/** SponsorBlock segment categories (subset of yt-dlp's, the ones Seal exposes). */
|
||||
export const SPONSORBLOCK_CATEGORIES = [
|
||||
'sponsor',
|
||||
'intro',
|
||||
'outro',
|
||||
'selfpromo',
|
||||
'preview',
|
||||
'filler',
|
||||
'interaction',
|
||||
'music_offtopic'
|
||||
] as const
|
||||
export type SponsorBlockCategory = (typeof SPONSORBLOCK_CATEGORIES)[number]
|
||||
|
||||
/**
|
||||
* Post-processing / format choices applied to a download. The persisted
|
||||
* defaults live in `Settings.downloadOptions`; an individual download may
|
||||
* override them via `StartDownloadOptions.options`.
|
||||
*/
|
||||
export interface DownloadOptions {
|
||||
/** audio extraction target (audio downloads only) */
|
||||
audioFormat: AudioFormat
|
||||
/** container for merged video downloads */
|
||||
videoContainer: VideoContainer
|
||||
/** preferred video codec (sort tiebreaker, not a hard filter) */
|
||||
preferredVideoCodec: VideoCodecPref
|
||||
/** download + embed subtitles into video */
|
||||
embedSubtitles: boolean
|
||||
/** comma-separated yt-dlp sub-lang selectors, e.g. 'en' or 'en.*,es' */
|
||||
subtitleLanguages: string
|
||||
/** also pull auto-generated (ASR) captions */
|
||||
autoSubtitles: boolean
|
||||
/** enable SponsorBlock */
|
||||
sponsorBlock: boolean
|
||||
sponsorBlockMode: SponsorBlockMode
|
||||
sponsorBlockCategories: SponsorBlockCategory[]
|
||||
/** embed chapter markers */
|
||||
embedChapters: boolean
|
||||
/** embed metadata (title/artist/etc.) */
|
||||
embedMetadata: boolean
|
||||
/** embed the thumbnail (cover art for audio, poster for mp4/mkv video) */
|
||||
embedThumbnail: boolean
|
||||
/** crop embedded audio artwork to a centred square (Seal's "crop artwork") */
|
||||
cropThumbnail: boolean
|
||||
}
|
||||
|
||||
export const DEFAULT_DOWNLOAD_OPTIONS: DownloadOptions = {
|
||||
audioFormat: 'mp3',
|
||||
videoContainer: 'mp4',
|
||||
preferredVideoCodec: 'any',
|
||||
embedSubtitles: false,
|
||||
subtitleLanguages: 'en',
|
||||
autoSubtitles: false,
|
||||
sponsorBlock: false,
|
||||
sponsorBlockMode: 'remove',
|
||||
sponsorBlockCategories: ['sponsor'],
|
||||
embedChapters: false,
|
||||
embedMetadata: true,
|
||||
embedThumbnail: true,
|
||||
cropThumbnail: false
|
||||
}
|
||||
|
||||
export interface YtdlpVersionResult {
|
||||
ok: boolean
|
||||
/** yt-dlp version string, present when ok is true */
|
||||
@@ -59,9 +137,35 @@ export interface MediaInfo {
|
||||
formats: FormatOption[]
|
||||
}
|
||||
|
||||
/** One entry of a probed playlist (flat — no per-entry format list). */
|
||||
export interface PlaylistEntry {
|
||||
/** 1-based position in the playlist */
|
||||
index: number
|
||||
id: string
|
||||
title: string
|
||||
/** canonical URL for the entry, enqueued as its own single-video download */
|
||||
url: string
|
||||
durationLabel?: string
|
||||
uploader?: string
|
||||
}
|
||||
|
||||
/** A probed playlist: lightweight metadata for each entry. */
|
||||
export interface PlaylistInfo {
|
||||
title: string
|
||||
uploader?: string
|
||||
count: number
|
||||
entries: PlaylistEntry[]
|
||||
}
|
||||
|
||||
/**
|
||||
* A single probe can resolve to either one video (with formats) or a playlist
|
||||
* (with entries). `kind` discriminates which field is populated.
|
||||
*/
|
||||
export interface ProbeResult {
|
||||
ok: boolean
|
||||
kind?: 'video' | 'playlist'
|
||||
info?: MediaInfo
|
||||
playlist?: PlaylistInfo
|
||||
error?: string
|
||||
}
|
||||
|
||||
@@ -78,6 +182,8 @@ export interface StartDownloadOptions {
|
||||
formatId?: string
|
||||
/** whether the chosen format already includes audio (skips +bestaudio) */
|
||||
formatHasAudio?: boolean
|
||||
/** per-download post-processing override; main falls back to settings defaults */
|
||||
options?: DownloadOptions
|
||||
}
|
||||
|
||||
export interface StartDownloadResult {
|
||||
@@ -126,6 +232,8 @@ export interface Settings {
|
||||
theme: 'light' | 'dark'
|
||||
/** auto-detect video links from the clipboard on window focus */
|
||||
clipboardWatch: boolean
|
||||
/** default post-processing / format options for new downloads */
|
||||
downloadOptions: DownloadOptions
|
||||
}
|
||||
|
||||
/** A completed download, persisted to history.json (newest-first). */
|
||||
|
||||
Reference in New Issue
Block a user