Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions docs/CHANGELOG.vi.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 3 additions & 0 deletions docs/CHANGELOG.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,9 @@ Youwee 的所有重要更改都将记录在此文件中。

## [Unreleased]

### 修复
- **浏览器扩展下载设置** - 浏览器扩展启动下载时保留 Youwee 的全局设置,例如容器、输出目录、播放列表和字幕

## [0.20.3] - 2026-08-23

### 修复
Expand Down
23 changes: 2 additions & 21 deletions src/contexts/DownloadContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import {
waitWithCancellation,
} from '@/lib/download-retry';
import {
buildExternalDownloadSettingsOverrides,
buildItemDownloadSettingsSnapshot,
createDefaultDownloadSettings,
refreshItemPluginWorkflowSnapshots,
Expand Down Expand Up @@ -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 = {
Expand Down
5 changes: 4 additions & 1 deletion src/contexts/UniversalContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
31 changes: 31 additions & 0 deletions src/lib/download-settings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
} from '@/lib/download-retry';
import type {
DownloadSettings,
ExternalEnqueueOptions,
FilenameMetadataField,
ItemDownloadSettings,
ItemUniversalSettings,
Expand Down Expand Up @@ -118,6 +119,36 @@ export function createDefaultDownloadSettings(saved: Partial<DownloadSettings>):
};
}

export function buildExternalDownloadSettingsOverrides(
settings: DownloadSettings,
options: ExternalEnqueueOptions | undefined,
outputPath: string,
): Partial<ItemDownloadSettings> {
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<DownloadSettings> {
return {
outputPath: settings.outputPath,
Expand Down
33 changes: 33 additions & 0 deletions tests/download-settings.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { describe, expect, test } from 'bun:test';
import {
buildExternalDownloadSettingsOverrides,
buildItemDownloadSettingsSnapshot,
createDefaultDownloadSettings,
refreshItemPluginWorkflowSnapshots,
Expand Down Expand Up @@ -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({});
Expand Down