설정 A 분야 7: 다운로드 · YouTube (DownloadSettings) - 키 12
- settings_group.dart 에 pickEnum (분야들의 읽기가 같이 씀 - fromJson 의 지역 함수 pick 대신) - AppSettings.resolvedDownloadRoot 는 download.root 를 씀 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>modular
parent
6054daf17a
commit
6beeb22e43
|
|
@ -65,7 +65,7 @@ void main() {
|
|||
|
||||
final c = AppController(PlatformServices(mediaTool: ProcessMediaTool('x', 'y'), storage: DesktopStorageService()));
|
||||
c.settings
|
||||
..downloadRoot = dir.path
|
||||
..download.root = dir.path
|
||||
..network.mode = forced ? 'custom' : 'system'
|
||||
..network.primary = '8.8.8.8'
|
||||
..network.secondary = '8.8.4.4';
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ void main() {
|
|||
await settle();
|
||||
await t.tap(find.text(tr('사용 안 함')).last);
|
||||
await settle(15);
|
||||
expect(c.settings.ytCookiesBrowser, '');
|
||||
expect(c.settings.download.ytCookiesBrowser, '');
|
||||
expect(cookie.existsSync(), isFalse, reason: '쓰지 않게 바꾸면 내보낸 쿠키 파일을 지움');
|
||||
expect(find.text(tr('쿠키 파일 지우기')), findsNothing, reason: '파일이 없고 쓰지 않으면 안내도 숨김');
|
||||
await shot('2_off');
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ void main() {
|
|||
final tmp = testTempDir('jj_dlcancel_');
|
||||
final d = DownloadManager(
|
||||
backends: [_Backend(DownloadKind.video), _Backend(DownloadKind.torrent)],
|
||||
settings: () => AppSettings()..downloadRoot = tmp.path,
|
||||
settings: () => AppSettings()..download.root = tmp.path,
|
||||
readClipboard: () async => null,
|
||||
);
|
||||
final shotKey = GlobalKey();
|
||||
|
|
|
|||
|
|
@ -57,7 +57,7 @@ void main() {
|
|||
addTearDown(() => i18n.apply('ko', save: false));
|
||||
final d = DownloadManager(
|
||||
backends: [_Quiet(DownloadKind.video), _Quiet(DownloadKind.torrent)],
|
||||
settings: () => AppSettings()..downloadRoot = r'D:\dl',
|
||||
settings: () => AppSettings()..download.root = r'D:\dl',
|
||||
readClipboard: () async => null,
|
||||
);
|
||||
// ① 메타데이터를 받는 중 (dn 이름 · 움직이는 "준비 중…") ② 멈춤 (이유 후보 · [다시 시도]) ③ 이름이 없는 마그넷
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ class AppController extends ChangeNotifier {
|
|||
/// 다운로드 폴더가 있는 드라이브 (예: "M:\\"). 설정이 바뀔 때만 다시 알아낸다.
|
||||
String? _diskFor, _diskRoot;
|
||||
String _downloadDisk() {
|
||||
final key = settings.downloadRoot ?? '';
|
||||
final key = settings.download.root ?? '';
|
||||
if (_diskRoot == null || _diskFor != key) {
|
||||
_diskFor = key;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ class DownloadManager extends ChangeNotifier {
|
|||
|
||||
/// 클립보드가 바뀌었으면 주소를 찾아 다운로드 추가
|
||||
Future<List<DownloadTask>> checkClipboard() async {
|
||||
if (!settings().clipboardWatch) return const [];
|
||||
if (!settings().download.clipboardWatch) return const [];
|
||||
final text = await _safeRead();
|
||||
if (text == null || text == _lastClip) return const [];
|
||||
_lastClip = text;
|
||||
|
|
@ -158,7 +158,7 @@ class DownloadManager extends ChangeNotifier {
|
|||
tasks.insert(0, t);
|
||||
_added.add(t);
|
||||
// YouTube 재생목록: 목록을 읽어 영상마다 한 줄씩 바꿔 넣는다
|
||||
if (link.kind == DownloadKind.video && s.ytExpandPlaylists && youtubePlaylistId(link.url) != null) {
|
||||
if (link.kind == DownloadKind.video && s.download.ytExpandPlaylists && youtubePlaylistId(link.url) != null) {
|
||||
t
|
||||
..expanding = true
|
||||
..title = trf('재생목록 불러오는 중… {0}', [link.url]);
|
||||
|
|
@ -270,7 +270,7 @@ class DownloadManager extends ChangeNotifier {
|
|||
|
||||
/// 동시 다운로드 수 안에서 대기 중인 작업을 들어온 순서대로 시작
|
||||
void _pump() {
|
||||
final limit = settings().maxParallelDownloads;
|
||||
final limit = settings().download.maxParallel;
|
||||
var running = tasks.where((t) => t.state == DownloadState.downloading).length;
|
||||
final waiting = tasks.where((t) => t.state == DownloadState.queued && !t.expanding).toList()
|
||||
..sort((a, b) => int.parse(a.id).compareTo(int.parse(b.id)));
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import '../services/app_shell.dart' show appIconOf;
|
|||
import '../core/remote_idle.dart';
|
||||
import 'settings/records.dart';
|
||||
import 'settings/settings_group.dart';
|
||||
import 'settings/download_settings.dart';
|
||||
import 'settings/reader_settings.dart';
|
||||
import 'settings/ai_image_settings.dart';
|
||||
import 'settings/copy_settings.dart';
|
||||
|
|
@ -25,6 +26,7 @@ import 'settings/explorer_settings.dart';
|
|||
export 'settings/records.dart';
|
||||
export 'settings/settings_group.dart';
|
||||
export 'settings/settings_store.dart';
|
||||
export 'settings/download_settings.dart';
|
||||
export 'settings/reader_settings.dart';
|
||||
export 'settings/ai_image_settings.dart';
|
||||
export 'settings/copy_settings.dart';
|
||||
|
|
@ -53,11 +55,7 @@ class AppSettings {
|
|||
/// 웹 브라우저 위에 다른 화면이 올라오거나 다른 화면으로 가면 페이지의 동영상 · 소리를 멈춤 (끄면 계속 들림)
|
||||
bool webPauseOnLeave = true;
|
||||
|
||||
/// 다운로드 위치 (아래에 jj_yt-dlp, jj_aria2 생성). null 이면 프로그램 폴더
|
||||
String? downloadRoot;
|
||||
|
||||
/// Ctrl+C 로 복사한 주소를 자동으로 다운로드
|
||||
bool clipboardWatch = true;
|
||||
|
||||
/// 최소화하면 작업 표시줄 대신 트레이로
|
||||
bool minimizeToTray = true;
|
||||
|
|
@ -72,36 +70,18 @@ class AppSettings {
|
|||
/// AI 자막: 매번 설정 창을 띄울지 (끄면 마지막 설정으로 바로 시작)
|
||||
bool askAiOptions = true;
|
||||
|
||||
/// 다운로드가 끝난 동영상을 편집 목록(MKV 만들기)에 자동으로 추가
|
||||
bool addFinishedDownloads = true;
|
||||
|
||||
/// 마그넷 · 토렌트가 이 초 동안 메타데이터 · 피어를 못 받으면 그 줄에 이유 후보 · [다시 시도] (0 = 알리지 않음, 관리자 지시 10/20)
|
||||
int torrentStallSeconds = 60;
|
||||
|
||||
/// OpenSubtitles (인터넷 자막 검색) - 키는 필수, 아이디·비밀번호는 선택 (받기 횟수 늘어남)
|
||||
String openSubtitlesKey = '';
|
||||
String openSubtitlesUser = '';
|
||||
String openSubtitlesPassword = '';
|
||||
|
||||
/// YouTube 받을 형식 · 화질 (기본: MP4 최고 화질)
|
||||
YtContainer ytContainer = YtContainer.mp4;
|
||||
YtQuality ytQuality = YtQuality.best;
|
||||
|
||||
/// 36: 같은 크기면 H.264 를 먼저 (휴대폰 · 태블릿은 AV1 · VP9 을 하드웨어로 못 푸는 일이 많음). 기본: Android 켜짐, PC 꺼짐
|
||||
bool ytPreferH264 = Platform.isAndroid;
|
||||
|
||||
/// 재생목록 주소면 목록 전체를 받기
|
||||
bool ytExpandPlaylists = true;
|
||||
|
||||
/// YouTube 로봇 확인 대응: 쿠키를 가져올 브라우저 (빈 값 = 사용 안 함) 또는 cookies.txt 경로
|
||||
String ytCookiesBrowser = internalBrowserCookies; // 기본: 앱 안 브라우저 로그인 사용
|
||||
|
||||
/// 53: 앱 안 브라우저 로그인 쿠키를 넘길 사이트 (처음은 모두 - 28)
|
||||
List<String> loginCookieSites = [...loginCookieDomains];
|
||||
String ytCookiesFile = '';
|
||||
|
||||
/// 동시에 받는 다운로드 수 (0 = 무제한)
|
||||
int maxParallelDownloads = 3;
|
||||
|
||||
/// 동시에 만드는 MKV 수 (0 = 무제한)
|
||||
int maxParallelJobs = 5;
|
||||
|
|
@ -420,8 +400,11 @@ class AppSettings {
|
|||
/// 보기 화면 (그림 · 만화 · PDF · 문서)
|
||||
final reader = ReaderSettings();
|
||||
|
||||
/// 다운로드 · YouTube (yt-dlp · aria2)
|
||||
final download = DownloadSettings();
|
||||
|
||||
/// 설정 분야들 (설정 A) - 저마다 자기 키를 읽고 쓴다. toJson · fromJson 은 이 목록을 돌며 합치기만 한다
|
||||
List<SettingsGroup> get groups => [explorer, network, tor, copy, aiImage, reader];
|
||||
List<SettingsGroup> get groups => [explorer, network, tor, copy, aiImage, reader, download];
|
||||
|
||||
/// [plainSecrets]: 안전 저장소를 쓸 수 없을 때만 (예전 파일에 있던 비밀 값을 잃지 않게) 예전처럼 파일에 둔다
|
||||
Map<String, Object?> toJson({bool plainSecrets = false}) {
|
||||
|
|
@ -434,14 +417,10 @@ class AppSettings {
|
|||
'youtubeAdSkip': youtubeAdSkip,
|
||||
'youtubeAdHide': youtubeAdHide,
|
||||
'webPauseOnLeave': webPauseOnLeave,
|
||||
'downloadRoot': downloadRoot,
|
||||
'clipboardWatch': clipboardWatch,
|
||||
'minimizeToTray': minimizeToTray,
|
||||
'closeAction': closeAction,
|
||||
'showHotkey': showHotkey,
|
||||
'askAiOptions': askAiOptions,
|
||||
'addFinishedDownloads': addFinishedDownloads,
|
||||
'torrentStallSeconds': torrentStallSeconds,
|
||||
// 비밀 값은 안전 저장소에 (54). 파일에는 저장해 두었는지만
|
||||
'openSubtitlesSaved': {
|
||||
'key': openSubtitlesKey.isNotEmpty,
|
||||
|
|
@ -453,15 +432,7 @@ class AppSettings {
|
|||
'openSubtitlesUser': openSubtitlesUser,
|
||||
'openSubtitlesPassword': openSubtitlesPassword,
|
||||
},
|
||||
'ytContainer': ytContainer.name,
|
||||
'ytQuality': ytQuality.name,
|
||||
'ytPreferH264': ytPreferH264,
|
||||
'ytExpandPlaylists': ytExpandPlaylists,
|
||||
'ytCookiesBrowser': ytCookiesBrowser,
|
||||
'loginCookieSites': loginCookieSites,
|
||||
'cookieScopeV2': true,
|
||||
'ytCookiesFile': ytCookiesFile,
|
||||
'maxParallelDownloads': maxParallelDownloads,
|
||||
'maxParallelJobs': maxParallelJobs,
|
||||
'showLog': showLog,
|
||||
'mkvCompactList': mkvCompactList,
|
||||
|
|
@ -574,27 +545,13 @@ class AppSettings {
|
|||
..youtubeAdSkip = j['youtubeAdSkip'] as bool? ?? true
|
||||
..youtubeAdHide = j['youtubeAdHide'] as bool? ?? true
|
||||
..webPauseOnLeave = j['webPauseOnLeave'] as bool? ?? true
|
||||
..downloadRoot = j['downloadRoot'] as String?
|
||||
..clipboardWatch = j['clipboardWatch'] as bool? ?? true
|
||||
..minimizeToTray = j['minimizeToTray'] as bool? ?? true
|
||||
..closeAction = const ['background', 'quit', 'ask'].contains(j['closeAction']) ? j['closeAction'] as String : 'background'
|
||||
..showHotkey = j['showHotkey'] as String? ?? 'Ctrl+Shift+X'
|
||||
..askAiOptions = j['askAiOptions'] as bool? ?? true
|
||||
..addFinishedDownloads = j['addFinishedDownloads'] as bool? ?? true
|
||||
..torrentStallSeconds = const [0, 30, 60, 120, 300].contains(j['torrentStallSeconds']) ? j['torrentStallSeconds'] as int : 60
|
||||
..openSubtitlesKey = j['openSubtitlesKey'] as String? ?? ''
|
||||
..openSubtitlesUser = j['openSubtitlesUser'] as String? ?? ''
|
||||
..openSubtitlesPassword = j['openSubtitlesPassword'] as String? ?? ''
|
||||
..ytContainer = pick(YtContainer.values, j['ytContainer'], YtContainer.mp4)
|
||||
..ytPreferH264 = j['ytPreferH264'] is bool ? j['ytPreferH264'] as bool : Platform.isAndroid
|
||||
..ytQuality = pick(YtQuality.values, j['ytQuality'], YtQuality.best)
|
||||
..ytExpandPlaylists = j['ytExpandPlaylists'] as bool? ?? true
|
||||
..ytCookiesBrowser = j['ytCookiesBrowser'] as String? ?? internalBrowserCookies
|
||||
..loginCookieSites = j['loginCookieSites'] is List
|
||||
? [for (final x in j['loginCookieSites'] as List) if (loginCookieDomains.contains(x)) '$x']
|
||||
: [...loginCookieDomains]
|
||||
..ytCookiesFile = j['ytCookiesFile'] as String? ?? ''
|
||||
..maxParallelDownloads = (j['maxParallelDownloads'] as num?)?.toInt() ?? 3
|
||||
..maxParallelJobs = (j['maxParallelJobs'] as num?)?.toInt() ?? 5
|
||||
..showLog = j['showLog'] as bool? ?? true
|
||||
..mkvCompactList = const ['auto', 'small', 'large'].contains(j['mkvCompactList']) ? j['mkvCompactList'] as String : 'auto'
|
||||
|
|
@ -803,7 +760,7 @@ class AppSettings {
|
|||
|
||||
/// 다운로드 기본 위치: 프로그램 폴더 (쓸 수 없으면 사용자 다운로드 폴더)
|
||||
String resolvedDownloadRoot() {
|
||||
if (downloadRoot != null && downloadRoot!.isNotEmpty) return downloadRoot!;
|
||||
if (download.root != null && download.root!.isNotEmpty) return download.root!;
|
||||
// Android: 프로그램 폴더가 없으므로 내장 저장소의 Download\JJ_MKVMaker
|
||||
if (Platform.isAndroid) return '/storage/emulated/0/Download/JJ_MKVMaker';
|
||||
final appDir = p.dirname(Platform.resolvedExecutable);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,76 @@
|
|||
import 'dart:io';
|
||||
|
||||
import '../../core/download_detect.dart';
|
||||
import 'settings_group.dart';
|
||||
|
||||
/// 약속 (설정 A): 환경 설정 > 다운로드 · YouTube (yt-dlp · aria2) - 자기 키 (이름 · 처음 값 · 읽기 · 쓰기) 를 스스로 갖는다.
|
||||
/// 설정 하나를 더할 때는 이 파일만 고친다. 저장 파일의 키 이름은 예전 그대로 (평평한 키)
|
||||
class DownloadSettings implements SettingsGroup {
|
||||
/// 다운로드 위치 (아래에 jj_yt-dlp, jj_aria2 생성). null 이면 프로그램 폴더
|
||||
String? root;
|
||||
|
||||
/// Ctrl+C 로 복사한 주소를 자동으로 다운로드
|
||||
bool clipboardWatch = true;
|
||||
|
||||
/// 다운로드가 끝난 동영상을 편집 목록(MKV 만들기)에 자동으로 추가
|
||||
bool addFinished = true;
|
||||
|
||||
/// 마그넷 · 토렌트가 이 초 동안 메타데이터 · 피어를 못 받으면 그 줄에 이유 후보 · [다시 시도] (0 = 알리지 않음, 관리자 지시 10/20)
|
||||
int torrentStallSeconds = 60;
|
||||
|
||||
/// YouTube 받을 형식 · 화질 (기본: MP4 최고 화질)
|
||||
YtContainer ytContainer = YtContainer.mp4;
|
||||
|
||||
YtQuality ytQuality = YtQuality.best;
|
||||
|
||||
/// 36: 같은 크기면 H.264 를 먼저 (휴대폰 · 태블릿은 AV1 · VP9 을 하드웨어로 못 푸는 일이 많음). 기본: Android 켜짐, PC 꺼짐
|
||||
bool ytPreferH264 = Platform.isAndroid;
|
||||
|
||||
/// 재생목록 주소면 목록 전체를 받기
|
||||
bool ytExpandPlaylists = true;
|
||||
|
||||
/// YouTube 로봇 확인 대응: 쿠키를 가져올 브라우저 (빈 값 = 사용 안 함) 또는 cookies.txt 경로
|
||||
String ytCookiesBrowser = internalBrowserCookies; // 기본: 앱 안 브라우저 로그인 사용
|
||||
|
||||
/// 53: 앱 안 브라우저 로그인 쿠키를 넘길 사이트 (처음은 모두 - 28)
|
||||
List<String> loginCookieSites = [...loginCookieDomains];
|
||||
|
||||
String ytCookiesFile = '';
|
||||
|
||||
/// 동시에 받는 다운로드 수 (0 = 무제한)
|
||||
int maxParallel = 3;
|
||||
|
||||
@override
|
||||
void write(Map<String, Object?> j, {required bool plainSecrets}) {
|
||||
j['downloadRoot'] = root;
|
||||
j['clipboardWatch'] = clipboardWatch;
|
||||
j['addFinishedDownloads'] = addFinished;
|
||||
j['torrentStallSeconds'] = torrentStallSeconds;
|
||||
j['ytContainer'] = ytContainer.name;
|
||||
j['ytQuality'] = ytQuality.name;
|
||||
j['ytPreferH264'] = ytPreferH264;
|
||||
j['ytExpandPlaylists'] = ytExpandPlaylists;
|
||||
j['ytCookiesBrowser'] = ytCookiesBrowser;
|
||||
j['loginCookieSites'] = loginCookieSites;
|
||||
j['ytCookiesFile'] = ytCookiesFile;
|
||||
j['maxParallelDownloads'] = maxParallel;
|
||||
}
|
||||
|
||||
@override
|
||||
void read(Map<String, dynamic> j) {
|
||||
root = j['downloadRoot'] as String?;
|
||||
clipboardWatch = j['clipboardWatch'] as bool? ?? true;
|
||||
addFinished = j['addFinishedDownloads'] as bool? ?? true;
|
||||
torrentStallSeconds = const [0, 30, 60, 120, 300].contains(j['torrentStallSeconds']) ? j['torrentStallSeconds'] as int : 60;
|
||||
ytContainer = pickEnum(YtContainer.values, j['ytContainer'], YtContainer.mp4);
|
||||
ytPreferH264 = j['ytPreferH264'] is bool ? j['ytPreferH264'] as bool : Platform.isAndroid;
|
||||
ytQuality = pickEnum(YtQuality.values, j['ytQuality'], YtQuality.best);
|
||||
ytExpandPlaylists = j['ytExpandPlaylists'] as bool? ?? true;
|
||||
ytCookiesBrowser = j['ytCookiesBrowser'] as String? ?? internalBrowserCookies;
|
||||
loginCookieSites = j['loginCookieSites'] is List
|
||||
? [for (final x in j['loginCookieSites'] as List) if (loginCookieDomains.contains(x)) '$x']
|
||||
: [...loginCookieDomains];
|
||||
ytCookiesFile = j['ytCookiesFile'] as String? ?? '';
|
||||
maxParallel = (j['maxParallelDownloads'] as num?)?.toInt() ?? 3;
|
||||
}
|
||||
}
|
||||
|
|
@ -8,3 +8,7 @@ abstract class SettingsGroup {
|
|||
/// 설정 파일 [j] 에서 읽는다 - 없거나 틀린 값은 처음 값 (또는 고친 값)
|
||||
void read(Map<String, dynamic> j);
|
||||
}
|
||||
|
||||
/// 저장된 이름으로 열거형 값 고르기 (모르는 이름 · 없으면 [fallback]) - 분야들의 읽기가 같이 쓴다
|
||||
T pickEnum<T extends Enum>(List<T> values, Object? name, T fallback) =>
|
||||
values.firstWhere((v) => v.name == name, orElse: () => fallback);
|
||||
|
|
|
|||
|
|
@ -298,7 +298,7 @@ Future<void> main(List<String> args) async {
|
|||
downloads.addListener(() => services.shell.setTooltip(syncAlert.tooltip()));
|
||||
// 다 받은 동영상 → 편집 목록 (설정에서 끌 수 있음)
|
||||
downloads.onFinished.listen((t) async {
|
||||
if (!controller.settings.addFinishedDownloads) return;
|
||||
if (!controller.settings.download.addFinished) return;
|
||||
final files = DownloadManager.videoFilesOf(t);
|
||||
if (files.isEmpty) return;
|
||||
final n = await controller.addDownloaded(files);
|
||||
|
|
@ -308,7 +308,7 @@ Future<void> main(List<String> args) async {
|
|||
showAddedToMkvNotice(messengerKey, navigatorKey, controller, title: t.title, files: files, added: n);
|
||||
});
|
||||
downloads.setAutoAdd = (on) async {
|
||||
await controller.updateSettings((x) => x.addFinishedDownloads = on);
|
||||
await controller.updateSettings((x) => x.download.addFinished = on);
|
||||
downloads.refresh();
|
||||
};
|
||||
downloads.useSystemDns = () async {
|
||||
|
|
@ -619,7 +619,7 @@ Future<void> runAndroid(String dataDir) async {
|
|||
watchDnsFailure(messengerKey, controller);
|
||||
// 다 받은 동영상 → 편집 목록 (설정에서 끌 수 있음)
|
||||
downloads.onFinished.listen((t) async {
|
||||
if (!controller.settings.addFinishedDownloads) return;
|
||||
if (!controller.settings.download.addFinished) return;
|
||||
final files = DownloadManager.videoFilesOf(t);
|
||||
if (files.isEmpty) return;
|
||||
final n = await controller.addDownloaded(files);
|
||||
|
|
@ -628,7 +628,7 @@ Future<void> runAndroid(String dataDir) async {
|
|||
showAddedToMkvNotice(messengerKey, navigatorKey, controller, title: t.title, files: files, added: n);
|
||||
});
|
||||
downloads.setAutoAdd = (on) async {
|
||||
await controller.updateSettings((x) => x.addFinishedDownloads = on);
|
||||
await controller.updateSettings((x) => x.download.addFinished = on);
|
||||
downloads.refresh();
|
||||
};
|
||||
downloads.useSystemDns = () async {
|
||||
|
|
|
|||
|
|
@ -71,10 +71,10 @@ class AndroidDownloadTools {
|
|||
// --ffmpeg-location 은 파일 경로도 받는다 (libffmpeg.so = ffmpeg 실행 파일)
|
||||
ffmpegDir: t.ffmpeg,
|
||||
jsRuntime: 'quickjs:${t.quickjs}',
|
||||
formatArgs: () => ytDlpFormatArgs(s().ytContainer, s().ytQuality, preferH264: s().ytPreferH264),
|
||||
formatArgs: () => ytDlpFormatArgs(s().download.ytContainer, s().download.ytQuality, preferH264: s().download.ytPreferH264),
|
||||
cookieArgs: () => ytDlpCookieArgs(
|
||||
browser: s().ytCookiesBrowser,
|
||||
file: s().ytCookiesFile,
|
||||
browser: s().download.ytCookiesBrowser,
|
||||
file: s().download.ytCookiesFile,
|
||||
internalCookieFile: s().internalCookieFile,
|
||||
),
|
||||
fallbackCookieArgs: () =>
|
||||
|
|
@ -84,7 +84,7 @@ class AndroidDownloadTools {
|
|||
_Deferred(DownloadKind.torrent, () async {
|
||||
final t = await ready();
|
||||
return Aria2Backend(aria2c: t.aria2c, environment: t.environment, extraArgs: ['--ca-certificate=${t.cert}'],
|
||||
stallAfter: () => Duration(seconds: s().torrentStallSeconds));
|
||||
stallAfter: () => Duration(seconds: s().download.torrentStallSeconds));
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
|
|
|||
|
|
@ -97,17 +97,17 @@ class PlatformServices {
|
|||
createDownloadBackends: (s) => [
|
||||
YtDlpBackend(
|
||||
ffmpegDir: ffmpegDir,
|
||||
formatArgs: () => ytDlpFormatArgs(s().ytContainer, s().ytQuality, preferH264: s().ytPreferH264),
|
||||
formatArgs: () => ytDlpFormatArgs(s().download.ytContainer, s().download.ytQuality, preferH264: s().download.ytPreferH264),
|
||||
cookieArgs: () => ytDlpCookieArgs(
|
||||
browser: s().ytCookiesBrowser,
|
||||
file: s().ytCookiesFile,
|
||||
browser: s().download.ytCookiesBrowser,
|
||||
file: s().download.ytCookiesFile,
|
||||
internalProfile: s().internalCookieProfile,
|
||||
internalCookieFile: s().internalCookieFile),
|
||||
// 고른 브라우저의 쿠키를 못 읽으면 앱 안 브라우저의 쿠키로
|
||||
fallbackCookieArgs: () => ytDlpCookieArgs(
|
||||
browser: internalBrowserCookies, internalCookieFile: s().internalCookieFile),
|
||||
),
|
||||
Aria2Backend(stallAfter: () => Duration(seconds: s().torrentStallSeconds)),
|
||||
Aria2Backend(stallAfter: () => Duration(seconds: s().download.torrentStallSeconds)),
|
||||
],
|
||||
shell: DesktopShell(),
|
||||
updater: GitHubUpdater(),
|
||||
|
|
|
|||
|
|
@ -545,8 +545,8 @@ class _BrowserPageState extends State<BrowserPage> with RouteAware {
|
|||
/// 53: 쿠키를 쓰도록 정했을 때만 · 고른 사이트만 내보낸다
|
||||
void _applyCookieExport() {
|
||||
final s = widget.c.settings;
|
||||
CookieExport.enabled = s.ytCookiesBrowser == internalBrowserCookies;
|
||||
CookieExport.sites = s.loginCookieSites;
|
||||
CookieExport.enabled = s.download.ytCookiesBrowser == internalBrowserCookies;
|
||||
CookieExport.sites = s.download.loginCookieSites;
|
||||
}
|
||||
|
||||
/// 53: 쿠키 · 방문 기록 지우기 (확인 뒤). 다운로드용으로 내보낸 쿠키 파일도 지운다
|
||||
|
|
|
|||
|
|
@ -164,11 +164,11 @@ class _DownloadsPageState extends State<DownloadsPage> {
|
|||
message: tr('다 받으면 MKV 만들기의 동영상 목록에 자동으로 넣고, 이 다운로드 목록에서는 뺍니다 (받은 파일은 그대로)'),
|
||||
child: InkWell(
|
||||
borderRadius: BorderRadius.circular(4),
|
||||
onTap: () => d.setAutoAdd!(!d.settings().addFinishedDownloads),
|
||||
onTap: () => d.setAutoAdd!(!d.settings().download.addFinished),
|
||||
child: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
Checkbox(
|
||||
visualDensity: VisualDensity.compact,
|
||||
value: d.settings().addFinishedDownloads,
|
||||
value: d.settings().download.addFinished,
|
||||
onChanged: (v) => d.setAutoAdd!(v ?? false),
|
||||
),
|
||||
Padding(
|
||||
|
|
|
|||
|
|
@ -76,14 +76,14 @@ class _DownloadSettingsSectionState extends State<DownloadSettingsSection> {
|
|||
return Column(crossAxisAlignment: CrossAxisAlignment.stretch, mainAxisSize: MainAxisSize.min, children: [
|
||||
if (desk)
|
||||
SwitchListTile(
|
||||
value: s.clipboardWatch,
|
||||
onChanged: (v) => c.updateSettings((x) => x.clipboardWatch = v),
|
||||
value: s.download.clipboardWatch,
|
||||
onChanged: (v) => c.updateSettings((x) => x.download.clipboardWatch = v),
|
||||
title: Text(tr('복사(Ctrl+C)한 주소 자동 다운로드')),
|
||||
subtitle: Text(tr('YouTube → yt-dlp, 마그넷 · .torrent → aria2 (완료 후 올려 주기 안 함)')),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.addFinishedDownloads,
|
||||
onChanged: (v) => c.updateSettings((x) => x.addFinishedDownloads = v),
|
||||
value: s.download.addFinished,
|
||||
onChanged: (v) => c.updateSettings((x) => x.download.addFinished = v),
|
||||
title: Text(tr('완료 시 자동 동영상 추가')),
|
||||
subtitle: Text(tr('다 받는 대로 MKV 만들기의 동영상 목록에 넣고, 다운로드 목록에서는 뺍니다 (다운로드 화면의 체크 상자와 같음)')),
|
||||
),
|
||||
|
|
@ -92,59 +92,59 @@ class _DownloadSettingsSectionState extends State<DownloadSettingsSection> {
|
|||
title: Text(tr('마그넷 · 토렌트가 시작되지 않으면 알리기')),
|
||||
subtitle: Text(tr('메타데이터나 피어를 이 시간 동안 못 받으면 그 줄에 이유 후보와 [다시 시도] 를 보입니다')),
|
||||
trailing: DropdownButton<int>(
|
||||
value: s.torrentStallSeconds,
|
||||
value: s.download.torrentStallSeconds,
|
||||
items: [
|
||||
for (final v in const [30, 60, 120, 300]) DropdownMenuItem(value: v, child: Text(trf('{0}초', [v]))),
|
||||
DropdownMenuItem(value: 0, child: Text(tr('알리지 않음'))),
|
||||
],
|
||||
onChanged: (v) => c.updateSettings((x) => x.torrentStallSeconds = v!),
|
||||
onChanged: (v) => c.updateSettings((x) => x.download.torrentStallSeconds = v!),
|
||||
),
|
||||
),
|
||||
SettingTile(
|
||||
title: Text(tr('YouTube 받을 형식')),
|
||||
subtitle: Text(tr('기본: MP4. 음성만 받으면 MP3 · M4A 로 저장')),
|
||||
trailing: DropdownButton<YtContainer>(
|
||||
value: s.ytContainer,
|
||||
value: s.download.ytContainer,
|
||||
items: [for (final v in YtContainer.values) DropdownMenuItem(value: v, child: Text(v.label))],
|
||||
onChanged: (v) => c.updateSettings((x) => x.ytContainer = v!),
|
||||
onChanged: (v) => c.updateSettings((x) => x.download.ytContainer = v!),
|
||||
),
|
||||
),
|
||||
SettingTile(
|
||||
enabled: !s.ytContainer.audioOnly,
|
||||
enabled: !s.download.ytContainer.audioOnly,
|
||||
title: Text(tr('YouTube 화질')),
|
||||
subtitle: Text(tr('고른 크기 이하에서 가장 좋은 화질 (영상에 없는 크기면 가까운 것)')),
|
||||
trailing: DropdownButton<YtQuality>(
|
||||
value: s.ytQuality,
|
||||
value: s.download.ytQuality,
|
||||
items: [for (final v in YtQuality.values) DropdownMenuItem(value: v, child: Text(v.label))],
|
||||
onChanged: s.ytContainer.audioOnly ? null : (v) => c.updateSettings((x) => x.ytQuality = v!),
|
||||
onChanged: s.download.ytContainer.audioOnly ? null : (v) => c.updateSettings((x) => x.download.ytQuality = v!),
|
||||
),
|
||||
),
|
||||
// 36: 같은 크기면 H.264 먼저 (Android 기본 켜짐)
|
||||
SwitchListTile(
|
||||
value: s.ytPreferH264,
|
||||
onChanged: s.ytContainer.audioOnly ? null : (v) => c.updateSettings((x) => x.ytPreferH264 = v),
|
||||
value: s.download.ytPreferH264,
|
||||
onChanged: s.download.ytContainer.audioOnly ? null : (v) => c.updateSettings((x) => x.download.ytPreferH264 = v),
|
||||
title: Text(tr('같은 화질이면 H.264 먼저 받기')),
|
||||
subtitle: Text(tr('휴대폰 · 태블릿은 AV1 · VP9 을 하드웨어로 풀지 못해 끊기거나 배터리를 많이 쓰는 일이 있습니다. '
|
||||
'YouTube 의 H.264 는 1080p 까지라 더 큰 화질을 고르면 크기가 먼저입니다.')),
|
||||
),
|
||||
SwitchListTile(
|
||||
value: s.ytExpandPlaylists,
|
||||
onChanged: (v) => c.updateSettings((x) => x.ytExpandPlaylists = v),
|
||||
value: s.download.ytExpandPlaylists,
|
||||
onChanged: (v) => c.updateSettings((x) => x.download.ytExpandPlaylists = v),
|
||||
title: Text(tr('재생목록 주소면 목록 전체 받기')),
|
||||
subtitle: Text(tr('영상마다 한 줄씩 보여 주고 jj_yt-dlp\\재생목록 이름\\ 에 저장 (믹스 목록 제외)')),
|
||||
),
|
||||
SettingTile(
|
||||
title: Text(tr('YouTube 쿠키 (로봇 확인이 나올 때)')),
|
||||
subtitle: Text(s.ytCookiesFile.isNotEmpty
|
||||
? 'cookies.txt: ${s.ytCookiesFile}'
|
||||
: s.ytCookiesBrowser == internalBrowserCookies
|
||||
subtitle: Text(s.download.ytCookiesFile.isNotEmpty
|
||||
? 'cookies.txt: ${s.download.ytCookiesFile}'
|
||||
: s.download.ytCookiesBrowser == internalBrowserCookies
|
||||
? tr('앱 안 브라우저의 로그인 사용 (브라우저 화면에서 YouTube 에 로그인하세요)')
|
||||
: s.ytCookiesBrowser.isNotEmpty
|
||||
? trf('{0} 브라우저의 YouTube 로그인 쿠키 사용 (Chrome · Edge 는 브라우저를 닫아야 읽힐 수 있음)', [s.ytCookiesBrowser])
|
||||
: s.download.ytCookiesBrowser.isNotEmpty
|
||||
? trf('{0} 브라우저의 YouTube 로그인 쿠키 사용 (Chrome · Edge 는 브라우저를 닫아야 읽힐 수 있음)', [s.download.ytCookiesBrowser])
|
||||
: tr('사용 안 함. "Sign in to confirm you\'re not a bot" 오류가 나오면 설정하세요')),
|
||||
trailing: Row(mainAxisSize: MainAxisSize.min, children: [
|
||||
DropdownButton<String>(
|
||||
value: s.ytCookiesFile.isNotEmpty ? '*file' : s.ytCookiesBrowser,
|
||||
value: s.download.ytCookiesFile.isNotEmpty ? '*file' : s.download.ytCookiesBrowser,
|
||||
items: [
|
||||
DropdownMenuItem(value: '', child: Text(tr('사용 안 함'))),
|
||||
DropdownMenuItem(value: internalBrowserCookies, child: Text(tr('앱 안 브라우저 (권장)'))),
|
||||
|
|
@ -162,16 +162,16 @@ class _DownloadSettingsSectionState extends State<DownloadSettingsSection> {
|
|||
allowedExtensions: const ['txt']);
|
||||
if (f?.path != null) {
|
||||
await c.updateSettings((x) => x
|
||||
..ytCookiesFile = f!.path!
|
||||
..ytCookiesBrowser = '');
|
||||
..download.ytCookiesFile = f!.path!
|
||||
..download.ytCookiesBrowser = '');
|
||||
}
|
||||
} else {
|
||||
await c.updateSettings((x) => x
|
||||
..ytCookiesBrowser = v ?? ''
|
||||
..ytCookiesFile = '');
|
||||
..download.ytCookiesBrowser = v ?? ''
|
||||
..download.ytCookiesFile = '');
|
||||
}
|
||||
// 131: 앱 안 브라우저 로그인을 더 쓰지 않으면 내보내 둔 평문 쿠키 파일을 지운다
|
||||
if (c.settings.ytCookiesBrowser != internalBrowserCookies) {
|
||||
if (c.settings.download.ytCookiesBrowser != internalBrowserCookies) {
|
||||
await deleteExportedCookies(c.settings.webViewDataDir);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
|
@ -181,7 +181,7 @@ class _DownloadSettingsSectionState extends State<DownloadSettingsSection> {
|
|||
),
|
||||
// 53: 앱 안 브라우저 쿠키 - 어디에 어떻게 저장되는지 · 넘길 사이트 고르기 · 지우기
|
||||
// 131: 쓰지 않게 바꿨어도 파일이 남아 있으면 지우는 버튼은 보인다
|
||||
if (s.ytCookiesBrowser == internalBrowserCookies || s.internalCookieFile.isNotEmpty)
|
||||
if (s.download.ytCookiesBrowser == internalBrowserCookies || s.internalCookieFile.isNotEmpty)
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 0, 16, 8),
|
||||
child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [
|
||||
|
|
@ -195,9 +195,9 @@ class _DownloadSettingsSectionState extends State<DownloadSettingsSection> {
|
|||
for (final d in loginCookieDomains)
|
||||
FilterChip(
|
||||
label: Text(d),
|
||||
selected: s.loginCookieSites.contains(d),
|
||||
onSelected: (on) => c.updateSettings((x) => x.loginCookieSites = [
|
||||
for (final y in x.loginCookieSites) if (y != d) y,
|
||||
selected: s.download.loginCookieSites.contains(d),
|
||||
onSelected: (on) => c.updateSettings((x) => x.download.loginCookieSites = [
|
||||
for (final y in x.download.loginCookieSites) if (y != d) y,
|
||||
if (on) d,
|
||||
]),
|
||||
),
|
||||
|
|
@ -220,19 +220,19 @@ class _DownloadSettingsSectionState extends State<DownloadSettingsSection> {
|
|||
title: Text(tr('동시 다운로드 수')),
|
||||
subtitle: Text(tr('나머지는 대기했다가 차례로 받습니다')),
|
||||
trailing: CountSelector(
|
||||
value: s.maxParallelDownloads,
|
||||
onChanged: (n) => c.updateSettings((x) => x.maxParallelDownloads = n),
|
||||
value: s.download.maxParallel,
|
||||
onChanged: (n) => c.updateSettings((x) => x.download.maxParallel = n),
|
||||
),
|
||||
),
|
||||
settingsFolderTile(
|
||||
title: tr('다운로드 (jj_yt-dlp · jj_aria2)'),
|
||||
value: s.downloadRoot,
|
||||
value: s.download.root,
|
||||
defaultText: trf('{0} (기본): {1}', [desk ? tr('프로그램 폴더') : tr('내장 저장소'), s.resolvedDownloadRoot()]),
|
||||
onPick: () async {
|
||||
final d = await pickFolder(context, tr('다운로드 위치'), s.resolvedDownloadRoot());
|
||||
if (d != null) await c.updateSettings((x) => x.downloadRoot = d);
|
||||
if (d != null) await c.updateSettings((x) => x.download.root = d);
|
||||
},
|
||||
onReset: () => c.updateSettings((x) => x.downloadRoot = null),
|
||||
onReset: () => c.updateSettings((x) => x.download.root = null),
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -54,9 +54,9 @@ void main() {
|
|||
});
|
||||
|
||||
test('설정: 시작되지 않으면 알리기 - 처음 60초, 모르는 값은 60', () {
|
||||
expect(AppSettings().torrentStallSeconds, 60);
|
||||
expect(AppSettings.fromJson({'torrentStallSeconds': 0}).torrentStallSeconds, 0);
|
||||
expect(AppSettings.fromJson({'torrentStallSeconds': 7}).torrentStallSeconds, 60);
|
||||
expect(AppSettings().download.torrentStallSeconds, 60);
|
||||
expect(AppSettings.fromJson({'torrentStallSeconds': 0}).download.torrentStallSeconds, 0);
|
||||
expect(AppSettings.fromJson({'torrentStallSeconds': 7}).download.torrentStallSeconds, 60);
|
||||
});
|
||||
|
||||
testWidgets('멈춘 마그넷 줄: "트래커 · 피어에 닿지 않습니다" · 이유 후보 · [다시 시도] (지금 것을 지우고 새로 넣음)', (tester) async {
|
||||
|
|
|
|||
|
|
@ -516,7 +516,7 @@ void main() {
|
|||
File(file).writeAsStringSync('x');
|
||||
|
||||
final d = DownloadManager(
|
||||
backends: [_Backend()], settings: () => AppSettings()..downloadRoot = dir.path, readClipboard: () async => null);
|
||||
backends: [_Backend()], settings: () => AppSettings()..download.root = dir.path, readClipboard: () async => null);
|
||||
final added = <String>[];
|
||||
d.addToEditList = (files) async {
|
||||
added.addAll(files);
|
||||
|
|
@ -552,11 +552,11 @@ void main() {
|
|||
tester.view.physicalSize = const Size(1500, 900);
|
||||
tester.view.devicePixelRatio = 1;
|
||||
addTearDown(tester.view.reset);
|
||||
final s = AppSettings()..addFinishedDownloads = false;
|
||||
final s = AppSettings()..download.addFinished = false;
|
||||
final d = DownloadManager(backends: [_Backend()], settings: () => s, readClipboard: () async => null);
|
||||
d.addToEditList = (files) async => files.length;
|
||||
d.setAutoAdd = (on) async {
|
||||
s.addFinishedDownloads = on;
|
||||
s.download.addFinished = on;
|
||||
d.refresh();
|
||||
};
|
||||
final t = d.addPage('https://vimeo.com/1')!..title = '영상';
|
||||
|
|
@ -567,7 +567,7 @@ void main() {
|
|||
expect(box().value, isFalse);
|
||||
await tester.tap(find.text('완료 시 자동 동영상 추가'));
|
||||
await tester.pump();
|
||||
expect(s.addFinishedDownloads, isTrue);
|
||||
expect(s.download.addFinished, isTrue);
|
||||
expect(box().value, isTrue);
|
||||
|
||||
// 다 받으면 목록에서 뺀다 (앱이 onFinished 에서 부름)
|
||||
|
|
@ -593,7 +593,7 @@ void main() {
|
|||
final d = DownloadManager(backends: [_Backend()], settings: () => s, readClipboard: () async => null);
|
||||
addTearDown(d.dispose);
|
||||
d.addToEditList = (files) async => files.length;
|
||||
d.setAutoAdd = (on) async => s.addFinishedDownloads = on;
|
||||
d.setAutoAdd = (on) async => s.download.addFinished = on;
|
||||
d.addPage('https://vimeo.com/1')!.title = '영상';
|
||||
await tester.pumpWidget(MaterialApp(home: DownloadsPage(d: d)));
|
||||
await tester.pump();
|
||||
|
|
|
|||
|
|
@ -187,7 +187,7 @@ void main() {
|
|||
expect(isCookieReadError('ERROR: Failed to decrypt with DPAPI. See cookies issue'), isTrue);
|
||||
expect(isCookieReadError('ERROR: Video unavailable'), isFalse);
|
||||
expect(ytDlpCookieArgs(browser: internalBrowserCookies), isEmpty); // 아직 로그인 전
|
||||
expect(AppSettings().ytCookiesBrowser, internalBrowserCookies); // 새 설치 기본값
|
||||
expect(AppSettings().download.ytCookiesBrowser, internalBrowserCookies); // 새 설치 기본값
|
||||
});
|
||||
test('cookies.txt 형식 (HttpOnly · 중복 제거 · 쿠키 파일 우선)', () {
|
||||
final txt = toNetscapeCookies(const [
|
||||
|
|
@ -208,7 +208,7 @@ void main() {
|
|||
|
||||
test('브라우저 페이지 받기 (YouTube 외 사이트도)', () {
|
||||
final v = _Backend(DownloadKind.video);
|
||||
final d = DownloadManager(backends: [v], settings: () => AppSettings()..downloadRoot = r'D:\dl');
|
||||
final d = DownloadManager(backends: [v], settings: () => AppSettings()..download.root = r'D:\dl');
|
||||
expect(d.addPage('https://vimeo.com/12345')!.kind, DownloadKind.video);
|
||||
expect(d.addPage('https://vimeo.com/12345'), isNull); // 이미 받는 중
|
||||
expect(d.addPage('about:blank'), isNull);
|
||||
|
|
@ -218,7 +218,7 @@ void main() {
|
|||
|
||||
test('다 받은 영상 → 편집 목록 알림 (한 번만, 조각 파일 제외)', () async {
|
||||
final v = _Backend(DownloadKind.video);
|
||||
final d = DownloadManager(backends: [v], settings: () => AppSettings()..downloadRoot = r'D:\dl');
|
||||
final d = DownloadManager(backends: [v], settings: () => AppSettings()..download.root = r'D:\dl');
|
||||
final got = <String>[];
|
||||
d.onFinished.listen((t) => got.add(t.id));
|
||||
final t = d.addPage('https://vimeo.com/1')!;
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ void main() {
|
|||
storage: _Storage(tmp),
|
||||
models: ModelStore(models),
|
||||
));
|
||||
c.settings.downloadRoot = p.join(root.path, 'dl');
|
||||
c.settings.download.root = p.join(root.path, 'dl');
|
||||
c.logFile = p.join(logs, 'app.log');
|
||||
|
||||
// 지울 것
|
||||
|
|
|
|||
|
|
@ -137,18 +137,18 @@ https://example.com/page.html
|
|||
final dir = Directory.systemTemp.createTempSync('jj_set_');
|
||||
addTearDown(() => dir.deleteSync(recursive: true));
|
||||
final store = SettingsStore(p.join(dir.path, 'settings.json'));
|
||||
expect((await store.load()).clipboardWatch, isTrue); // 파일 없으면 기본값
|
||||
expect((await store.load()).download.clipboardWatch, isTrue); // 파일 없으면 기본값
|
||||
final s = AppSettings()
|
||||
..mkvOutputRoot = r'D:\out'
|
||||
..downloadRoot = r'D:\dl'
|
||||
..clipboardWatch = false
|
||||
..download.root = r'D:\dl'
|
||||
..download.clipboardWatch = false
|
||||
..showHotkey = 'Alt+F9'
|
||||
..askAiOptions = false
|
||||
..encode = const EncodeSettings(codec: VideoCodecChoice.vp9, resolution: ResolutionChoice.p720)
|
||||
..aiTargets = ['ko', 'fr'];
|
||||
await store.save(s);
|
||||
final l = await store.load();
|
||||
expect([l.mkvOutputRoot, l.downloadRoot, l.clipboardWatch, l.showHotkey, l.askAiOptions],
|
||||
expect([l.mkvOutputRoot, l.download.root, l.download.clipboardWatch, l.showHotkey, l.askAiOptions],
|
||||
[r'D:\out', r'D:\dl', false, 'Alt+F9', false]);
|
||||
expect(l.encode.codec, VideoCodecChoice.vp9);
|
||||
expect(l.encode.resolution, ResolutionChoice.p720);
|
||||
|
|
@ -166,7 +166,7 @@ https://example.com/page.html
|
|||
setUp(() {
|
||||
video = _FakeBackend(DownloadKind.video);
|
||||
torrent = _FakeBackend(DownloadKind.torrent);
|
||||
settings = AppSettings()..downloadRoot = r'D:\dl';
|
||||
settings = AppSettings()..download.root = r'D:\dl';
|
||||
clip = 'https://youtu.be/already-there';
|
||||
d = DownloadManager(
|
||||
backends: [video, torrent], settings: () => settings, readClipboard: () async => clip);
|
||||
|
|
@ -193,7 +193,7 @@ https://example.com/page.html
|
|||
expect(video.started, ['https://www.youtube.com/watch?v=AAA111']);
|
||||
expect(torrent.started, hasLength(1));
|
||||
|
||||
settings.clipboardWatch = false;
|
||||
settings.download.clipboardWatch = false;
|
||||
clip = 'https://youtu.be/BBB222';
|
||||
expect(await d.checkClipboard(), isEmpty);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ class _Quiet extends _Backend {
|
|||
void main() {
|
||||
DownloadManager make() => DownloadManager(
|
||||
backends: [_Backend(DownloadKind.video), _Backend(DownloadKind.torrent)],
|
||||
settings: () => AppSettings()..downloadRoot = r'D:\dl',
|
||||
settings: () => AppSettings()..download.root = r'D:\dl',
|
||||
readClipboard: () async => null,
|
||||
);
|
||||
|
||||
|
|
@ -213,7 +213,7 @@ void main() {
|
|||
addTearDown(tester.view.reset);
|
||||
final d = DownloadManager(
|
||||
backends: [_Quiet(DownloadKind.video), _Quiet(DownloadKind.torrent)],
|
||||
settings: () => AppSettings()..downloadRoot = r'D:\dl',
|
||||
settings: () => AppSettings()..download.root = r'D:\dl',
|
||||
readClipboard: () async => null,
|
||||
);
|
||||
await tester.pumpWidget(MaterialApp(home: DownloadsPage(d: d)));
|
||||
|
|
|
|||
|
|
@ -113,8 +113,8 @@ void main() {
|
|||
setUp(() {
|
||||
video = _Backend(DownloadKind.video);
|
||||
settings = AppSettings()
|
||||
..downloadRoot = r'D:\dl'
|
||||
..maxParallelDownloads = 2;
|
||||
..download.root = r'D:\dl'
|
||||
..download.maxParallel = 2;
|
||||
d = DownloadManager(backends: [video], settings: () => settings, readClipboard: () async => null);
|
||||
});
|
||||
tearDown(() => d.dispose());
|
||||
|
|
@ -132,7 +132,7 @@ void main() {
|
|||
expect(ts[3].state, DownloadState.downloading);
|
||||
await d.resume([ts[1]]);
|
||||
expect(ts[1].state, DownloadState.queued);
|
||||
settings.maxParallelDownloads = 0; // 무한
|
||||
settings.download.maxParallel = 0; // 무한
|
||||
await d.resume(const []);
|
||||
expect(ts.where((t) => t.state == DownloadState.downloading), hasLength(4));
|
||||
});
|
||||
|
|
@ -153,7 +153,7 @@ void main() {
|
|||
expect(d.tasks.first.dir, p.join(r'D:\dl', 'jj_yt-dlp', 'My Playlist'));
|
||||
expect(d.tasks.contains(holder), isFalse);
|
||||
|
||||
settings.ytExpandPlaylists = false;
|
||||
settings.download.ytExpandPlaylists = false;
|
||||
final single = d.add('https://www.youtube.com/watch?v=z&list=PLother')!;
|
||||
expect(single.expanding, isFalse);
|
||||
});
|
||||
|
|
|
|||
|
|
@ -95,15 +95,15 @@ void main() {
|
|||
});
|
||||
|
||||
test('고른 사이트의 쿠키만 넘긴다 (처음은 모두)', () {
|
||||
expect(AppSettings().loginCookieSites, loginCookieDomains);
|
||||
expect(AppSettings().download.loginCookieSites, loginCookieDomains);
|
||||
expect(isLoginCookieDomain('.instagram.com'), isTrue);
|
||||
CookieExport.sites = ['youtube.com', 'google.com'];
|
||||
expect(isLoginCookieDomain('.youtube.com'), isTrue);
|
||||
expect(isLoginCookieDomain('.instagram.com'), isFalse);
|
||||
expect(isLoginCookieDomain('netflix.com'), isFalse);
|
||||
final b = AppSettings.fromJson((AppSettings()..loginCookieSites = ['x.com']).toJson());
|
||||
expect(b.loginCookieSites, ['x.com']);
|
||||
expect(AppSettings.fromJson({'loginCookieSites': ['x.com', 'evil.com']}).loginCookieSites, ['x.com']);
|
||||
final b = AppSettings.fromJson((AppSettings()..download.loginCookieSites = ['x.com']).toJson());
|
||||
expect(b.download.loginCookieSites, ['x.com']);
|
||||
expect(AppSettings.fromJson({'loginCookieSites': ['x.com', 'evil.com']}).download.loginCookieSites, ['x.com']);
|
||||
});
|
||||
|
||||
test('앱 안 브라우저 쿠키를 쓰던 사람에게 넘기는 사이트가 넓어진 것을 한 번 알린다', () {
|
||||
|
|
|
|||
|
|
@ -33,9 +33,9 @@ void main() {
|
|||
});
|
||||
|
||||
test('36: 같은 화질이면 H.264 먼저 - 설정 (PC 기본 꺼짐) · yt-dlp 인수', () {
|
||||
expect(AppSettings.fromJson({}).ytPreferH264, isFalse); // 시험은 PC (Android 는 기본 켜짐)
|
||||
expect(AppSettings.fromJson({'ytPreferH264': true}).ytPreferH264, isTrue);
|
||||
expect(AppSettings.fromJson((AppSettings()..ytPreferH264 = true).toJson()).ytPreferH264, isTrue);
|
||||
expect(AppSettings.fromJson({}).download.ytPreferH264, isFalse); // 시험은 PC (Android 는 기본 켜짐)
|
||||
expect(AppSettings.fromJson({'ytPreferH264': true}).download.ytPreferH264, isTrue);
|
||||
expect(AppSettings.fromJson((AppSettings()..download.ytPreferH264 = true).toJson()).download.ytPreferH264, isTrue);
|
||||
expect(ytDlpFormatArgs(YtContainer.mp4, YtQuality.best, preferH264: true).join(' '), contains('vcodec:h264'));
|
||||
expect(ytDlpFormatArgs(YtContainer.mp4, YtQuality.best).join(' '), isNot(contains('vcodec:h264')));
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue