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:
2026-06-22 19:32:59 -04:00
parent d4b9a0eefa
commit bb5dd6c438
21 changed files with 1101 additions and 91 deletions
+2 -3
View File
@@ -1,9 +1,8 @@
import { ElectronAPI } from '@electron-toolkit/preload'
import type { Api } from './index'
import type { Api, ElectronMarker } from './index'
declare global {
interface Window {
electron: ElectronAPI
electron: ElectronMarker
api: Api
}
}
+8 -3
View File
@@ -1,5 +1,4 @@
import { contextBridge, ipcRenderer, type IpcRendererEvent } from 'electron'
import { electronAPI } from '@electron-toolkit/preload'
import {
IpcChannels,
type YtdlpVersionResult,
@@ -60,16 +59,22 @@ const api = {
export type Api = typeof api
// A minimal presence marker. The renderer only checks `window.electron` for
// truthiness to tell the real app apart from the browser UI preview, so there's
// no reason to expose the full ipcRenderer/webFrame/process surface here.
const electron = { isElectron: true } as const
export type ElectronMarker = typeof electron
if (process.contextIsolated) {
try {
contextBridge.exposeInMainWorld('electron', electronAPI)
contextBridge.exposeInMainWorld('electron', electron)
contextBridge.exposeInMainWorld('api', api)
} catch (error) {
console.error(error)
}
} else {
// @ts-ignore (defined in index.d.ts)
window.electron = electronAPI
window.electron = electron
// @ts-ignore (defined in index.d.ts)
window.api = api
}