diff --git a/CHANGELOG.md b/CHANGELOG.md index e083e587..d8fd1e46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed +- **Browser extension download settings** - Preserve global Youwee download settings such as container, output folder, playlists, and subtitles when a browser extension starts a download + ## [0.20.3] - 2026-08-23 ### Fixed diff --git a/docs/CHANGELOG.vi.md b/docs/CHANGELOG.vi.md index 29c4aac0..fe27d74e 100644 --- a/docs/CHANGELOG.vi.md +++ b/docs/CHANGELOG.vi.md @@ -7,6 +7,9 @@ và dự án tuân theo [Semantic Versioning](https://semver.org/spec/v2.0.0.htm ## [Unreleased] +### Sửa lỗi +- **Cài đặt tải từ browser extension** - Giữ lại các cài đặt Youwee global như container, thư mục đầu ra, playlist và subtitle khi browser extension bắt đầu tải xuống + ## [0.20.3] - 2026-08-23 ### Sửa lỗi diff --git a/docs/CHANGELOG.zh-CN.md b/docs/CHANGELOG.zh-CN.md index 25424f31..813f9db8 100644 --- a/docs/CHANGELOG.zh-CN.md +++ b/docs/CHANGELOG.zh-CN.md @@ -7,6 +7,9 @@ Youwee 的所有重要更改都将记录在此文件中。 ## [Unreleased] +### 修复 +- **浏览器扩展下载设置** - 浏览器扩展启动下载时保留 Youwee 的全局设置,例如容器、输出目录、播放列表和字幕 + ## [0.20.3] - 2026-08-23 ### 修复 diff --git a/src/contexts/DownloadContext.tsx b/src/contexts/DownloadContext.tsx index e738cc4c..ec569284 100644 --- a/src/contexts/DownloadContext.tsx +++ b/src/contexts/DownloadContext.tsx @@ -34,6 +34,7 @@ import { waitWithCancellation, } from '@/lib/download-retry'; import { + buildExternalDownloadSettingsOverrides, buildItemDownloadSettingsSnapshot, createDefaultDownloadSettings, refreshItemPluginWorkflowSnapshots, @@ -820,30 +821,10 @@ export function DownloadProvider({ children }: { children: ReactNode }) { } } const workflowSnapshots = loadPluginWorkflowSnapshots(); - const mediaType = options?.mediaType === 'audio' ? 'audio' : 'video'; - const videoQuality = - options?.quality && options.quality !== 'audio' ? options.quality : 'best'; - const audioBitrate = options?.audioBitrate === '128' ? '128' : 'auto'; - const settingsSnapshot = buildItemDownloadSettingsSnapshot(currentSettings, { pluginWorkflowSnapshots: workflowSnapshots, postDownloadWorkflowSteps: loadPostDownloadWorkflowSteps(), - overrides: { - quality: mediaType === 'audio' ? 'audio' : videoQuality, - format: mediaType === 'audio' ? 'mp3' : 'mp4', - outputPath, - downloadPlaylist: options?.downloadPlaylist ?? false, - playlistLimit: options?.playlistLimit ?? null, - audioBitrate: mediaType === 'audio' ? audioBitrate : currentSettings.audioBitrate, - subtitleMode: options?.subtitleMode ?? currentSettings.subtitleMode, - subtitleLangs: options?.subtitleLangs ?? [...currentSettings.subtitleLangs], - subtitleEmbed: options?.subtitleEmbed ?? currentSettings.subtitleEmbed, - subtitleFormat: options?.subtitleFormat ?? currentSettings.subtitleFormat, - timeRangeStart: options?.timeRangeStart, - timeRangeEnd: options?.timeRangeEnd, - liveFromStart: options?.liveFromStart ?? currentSettings.liveFromStart, - skipLive: options?.skipLive ?? currentSettings.skipLive, - }, + overrides: buildExternalDownloadSettingsOverrides(currentSettings, options, outputPath), }); const newItem: DownloadItem = { diff --git a/src/contexts/UniversalContext.tsx b/src/contexts/UniversalContext.tsx index 33ba2d11..d165646c 100644 --- a/src/contexts/UniversalContext.tsx +++ b/src/contexts/UniversalContext.tsx @@ -667,7 +667,10 @@ export function UniversalProvider({ children }: { children: ReactNode }) { postDownloadWorkflowSteps: loadPostDownloadWorkflowSteps(), overrides: { quality: mediaType === 'audio' ? 'audio' : videoQuality, - format: mediaType === 'audio' ? 'mp3' : 'mp4', + // External callers choose media and quality only. Keep the current + // Universal container preference unless a future deep-link contract + // explicitly adds a format override. + format: currentSettings.format, outputPath, audioBitrate: mediaType === 'audio' ? audioBitrate : currentSettings.audioBitrate, timeRangeStart: options?.timeRangeStart, diff --git a/src/lib/download-settings.ts b/src/lib/download-settings.ts index 3c904cb2..0598f628 100644 --- a/src/lib/download-settings.ts +++ b/src/lib/download-settings.ts @@ -5,6 +5,7 @@ import { } from '@/lib/download-retry'; import type { DownloadSettings, + ExternalEnqueueOptions, FilenameMetadataField, ItemDownloadSettings, ItemUniversalSettings, @@ -118,6 +119,36 @@ export function createDefaultDownloadSettings(saved: Partial): }; } +export function buildExternalDownloadSettingsOverrides( + settings: DownloadSettings, + options: ExternalEnqueueOptions | undefined, + outputPath: string, +): Partial { + const mediaType = options?.mediaType === 'audio' ? 'audio' : 'video'; + const videoQuality = options?.quality && options.quality !== 'audio' ? options.quality : 'best'; + const audioBitrate = options?.audioBitrate === '128' ? '128' : 'auto'; + + return { + quality: mediaType === 'audio' ? 'audio' : videoQuality, + // Browser deep links select media and quality, not a container. Preserve + // the global format that will be snapshotted onto this queue item. + format: settings.format, + outputPath, + downloadPlaylist: options?.downloadPlaylist ?? settings.downloadPlaylist, + playlistLimit: + options?.playlistLimit ?? (settings.playlistLimit > 0 ? settings.playlistLimit : null), + audioBitrate: mediaType === 'audio' ? audioBitrate : settings.audioBitrate, + subtitleMode: options?.subtitleMode ?? settings.subtitleMode, + subtitleLangs: options?.subtitleLangs ?? [...settings.subtitleLangs], + subtitleEmbed: options?.subtitleEmbed ?? settings.subtitleEmbed, + subtitleFormat: options?.subtitleFormat ?? settings.subtitleFormat, + timeRangeStart: options?.timeRangeStart, + timeRangeEnd: options?.timeRangeEnd, + liveFromStart: options?.liveFromStart ?? settings.liveFromStart, + skipLive: options?.skipLive ?? settings.skipLive, + }; +} + export function serializeDownloadSettings(settings: DownloadSettings): Partial { return { outputPath: settings.outputPath, diff --git a/tests/download-settings.test.ts b/tests/download-settings.test.ts index 1a07d71b..a0438b72 100644 --- a/tests/download-settings.test.ts +++ b/tests/download-settings.test.ts @@ -1,5 +1,6 @@ import { describe, expect, test } from 'bun:test'; import { + buildExternalDownloadSettingsOverrides, buildItemDownloadSettingsSnapshot, createDefaultDownloadSettings, refreshItemPluginWorkflowSnapshots, @@ -88,6 +89,38 @@ describe('download settings playlist numbering and chapter split options', () => }); }); +describe('external download settings', () => { + test('inherits global video settings while keeping extension quality', () => { + const settings = createDefaultDownloadSettings({ + format: 'mkv', + outputPath: '/downloads', + downloadPlaylist: true, + playlistLimit: 12, + subtitleMode: 'auto', + subtitleLangs: ['en', 'ru'], + subtitleEmbed: true, + subtitleFormat: 'vtt', + }); + + const overrides = buildExternalDownloadSettingsOverrides( + settings, + { mediaType: 'video', quality: '1080' }, + settings.outputPath, + ); + const snapshot = buildItemDownloadSettingsSnapshot(settings, { overrides }); + + expect(snapshot.quality).toBe('1080'); + expect(snapshot.format).toBe('mkv'); + expect(snapshot.outputPath).toBe('/downloads'); + expect(snapshot.downloadPlaylist).toBe(true); + expect(snapshot.playlistLimit).toBe(12); + expect(snapshot.subtitleMode).toBe('auto'); + expect(snapshot.subtitleLangs).toEqual(['en', 'ru']); + expect(snapshot.subtitleEmbed).toBe(true); + expect(snapshot.subtitleFormat).toBe('vtt'); + }); +}); + describe('download settings downloaded video memory', () => { test('defaults downloaded video memory off and asks before adding duplicates', () => { const settings = createDefaultDownloadSettings({});