mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-20 00:49:33 +05:30
feat: added sponsorblock support and improved resume persistence
This commit is contained in:
@@ -68,5 +68,42 @@ pub fn get_migrations() -> Vec<Migration> {
|
|||||||
);
|
);
|
||||||
",
|
",
|
||||||
kind: MigrationKind::Up,
|
kind: MigrationKind::Up,
|
||||||
|
},
|
||||||
|
Migration {
|
||||||
|
version: 2,
|
||||||
|
description: "add_columns_to_downloads",
|
||||||
|
sql: "
|
||||||
|
ALTER TABLE downloads ADD COLUMN output_format TEXT;
|
||||||
|
ALTER TABLE downloads ADD COLUMN embed_metadata INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE downloads ADD COLUMN embed_thumbnail INTEGER NOT NULL DEFAULT 0;
|
||||||
|
ALTER TABLE downloads ADD COLUMN sponsorblock_remove TEXT;
|
||||||
|
ALTER TABLE downloads ADD COLUMN sponsorblock_mark TEXT;
|
||||||
|
ALTER TABLE downloads ADD COLUMN created_at TEXT;
|
||||||
|
ALTER TABLE downloads ADD COLUMN updated_at TEXT;
|
||||||
|
|
||||||
|
-- Update existing rows with current timestamp
|
||||||
|
UPDATE downloads SET created_at = CURRENT_TIMESTAMP WHERE created_at IS NULL;
|
||||||
|
UPDATE downloads SET updated_at = CURRENT_TIMESTAMP WHERE updated_at IS NULL;
|
||||||
|
|
||||||
|
CREATE TRIGGER IF NOT EXISTS update_downloads_updated_at
|
||||||
|
AFTER UPDATE ON downloads
|
||||||
|
FOR EACH ROW
|
||||||
|
BEGIN
|
||||||
|
UPDATE downloads SET updated_at = CURRENT_TIMESTAMP WHERE id = NEW.id;
|
||||||
|
END;
|
||||||
|
|
||||||
|
-- Create trigger for new inserts to set created_at and updated_at
|
||||||
|
CREATE TRIGGER IF NOT EXISTS set_downloads_timestamps
|
||||||
|
AFTER INSERT ON downloads
|
||||||
|
FOR EACH ROW
|
||||||
|
WHEN NEW.created_at IS NULL OR NEW.updated_at IS NULL
|
||||||
|
BEGIN
|
||||||
|
UPDATE downloads
|
||||||
|
SET created_at = COALESCE(NEW.created_at, CURRENT_TIMESTAMP),
|
||||||
|
updated_at = COALESCE(NEW.updated_at, CURRENT_TIMESTAMP)
|
||||||
|
WHERE id = NEW.id;
|
||||||
|
END;
|
||||||
|
",
|
||||||
|
kind: MigrationKind::Up,
|
||||||
}]
|
}]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,9 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"plugins": {
|
"plugins": {
|
||||||
|
"sql": {
|
||||||
|
"preload": ["sqlite:database.db"]
|
||||||
|
},
|
||||||
"updater": {
|
"updater": {
|
||||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDNDM0I4ODcyODdGOTM4MDIKUldRQ09QbUhjb2c3UENGY1lFUVdTVWhucmJ4QzdGeW9sU3VHVFlGNWY5anZab2s4SU1rMWFsekMK",
|
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDNDM0I4ODcyODdGOTM4MDIKUldRQ09QbUhjb2c3UENGY1lFUVdTVWhucmJ4QzdGeW9sU3VHVFlGNWY5anZab2s4SU1rMWFsekMK",
|
||||||
"endpoints": [
|
"endpoints": [
|
||||||
|
|||||||
83
src/App.tsx
83
src/App.tsx
@@ -74,6 +74,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
const IMPORT_COOKIES_FROM = useSettingsPageStatesStore(state => state.settings.import_cookies_from);
|
const IMPORT_COOKIES_FROM = useSettingsPageStatesStore(state => state.settings.import_cookies_from);
|
||||||
const COOKIES_BROWSER = useSettingsPageStatesStore(state => state.settings.cookies_browser);
|
const COOKIES_BROWSER = useSettingsPageStatesStore(state => state.settings.cookies_browser);
|
||||||
const COOKIES_FILE = useSettingsPageStatesStore(state => state.settings.cookies_file);
|
const COOKIES_FILE = useSettingsPageStatesStore(state => state.settings.cookies_file);
|
||||||
|
const USE_SPONSORBLOCK = useSettingsPageStatesStore(state => state.settings.use_sponsorblock);
|
||||||
|
const SPONSORBLOCK_MODE = useSettingsPageStatesStore(state => state.settings.sponsorblock_mode);
|
||||||
|
const SPONSORBLOCK_REMOVE = useSettingsPageStatesStore(state => state.settings.sponsorblock_remove);
|
||||||
|
const SPONSORBLOCK_MARK = useSettingsPageStatesStore(state => state.settings.sponsorblock_mark);
|
||||||
|
const SPONSORBLOCK_REMOVE_CATEGORIES = useSettingsPageStatesStore(state => state.settings.sponsorblock_remove_categories);
|
||||||
|
const SPONSORBLOCK_MARK_CATEGORIES = useSettingsPageStatesStore(state => state.settings.sponsorblock_mark_categories);
|
||||||
|
|
||||||
const isErrored = useDownloaderPageStatesStore((state) => state.isErrored);
|
const isErrored = useDownloaderPageStatesStore((state) => state.isErrored);
|
||||||
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
||||||
@@ -205,6 +211,8 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
if (AUDIO_FORMAT !== 'auto' && fileType === 'audio') videoMetadata.ext = AUDIO_FORMAT;
|
if (AUDIO_FORMAT !== 'auto' && fileType === 'audio') videoMetadata.ext = AUDIO_FORMAT;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (resumeState && resumeState.output_format) videoMetadata.ext = resumeState.output_format;
|
||||||
|
|
||||||
const videoId = resumeState?.video_id || generateVideoId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
const videoId = resumeState?.video_id || generateVideoId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
||||||
const playlistId = isPlaylist ? (resumeState?.playlist_id || generateVideoId(videoMetadata.playlist_id, videoMetadata.webpage_url_domain)) : null;
|
const playlistId = isPlaylist ? (resumeState?.playlist_id || generateVideoId(videoMetadata.playlist_id, videoMetadata.webpage_url_domain)) : null;
|
||||||
const downloadId = resumeState?.download_id || generateDownloadId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
const downloadId = resumeState?.download_id || generateDownloadId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
||||||
@@ -237,45 +245,46 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
args.push('--playlist-items', playlistIndex);
|
args.push('--playlist-items', playlistIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileType !== 'unknown' && (VIDEO_FORMAT !== 'auto' || AUDIO_FORMAT !== 'auto')) {
|
let outputFormat = null;
|
||||||
if (VIDEO_FORMAT !== 'auto' && fileType === 'video+audio') {
|
if (fileType !== 'unknown' && ((VIDEO_FORMAT !== 'auto' || AUDIO_FORMAT !== 'auto') || resumeState?.output_format)) {
|
||||||
|
outputFormat = resumeState?.output_format || (fileType === 'video+audio' ? VIDEO_FORMAT : (fileType === 'video' ? VIDEO_FORMAT : (fileType === 'audio' ? AUDIO_FORMAT : null)));
|
||||||
|
if ((VIDEO_FORMAT !== 'auto' && fileType === 'video+audio') || (resumeState?.output_format && fileType === 'video+audio')) {
|
||||||
if (ALWAYS_REENCODE_VIDEO) {
|
if (ALWAYS_REENCODE_VIDEO) {
|
||||||
args.push('--recode-video', VIDEO_FORMAT);
|
args.push('--recode-video', resumeState?.output_format || VIDEO_FORMAT);
|
||||||
} else {
|
} else {
|
||||||
args.push('--merge-output-format', VIDEO_FORMAT);
|
args.push('--merge-output-format', resumeState?.output_format || VIDEO_FORMAT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (VIDEO_FORMAT !== 'auto' && fileType === 'video') {
|
if ((VIDEO_FORMAT !== 'auto' && fileType === 'video') || (resumeState?.output_format && fileType === 'video')) {
|
||||||
if (ALWAYS_REENCODE_VIDEO) {
|
if (ALWAYS_REENCODE_VIDEO) {
|
||||||
args.push('--recode-video', VIDEO_FORMAT);
|
args.push('--recode-video', resumeState?.output_format || VIDEO_FORMAT);
|
||||||
} else {
|
} else {
|
||||||
args.push('--remux-video', VIDEO_FORMAT);
|
args.push('--remux-video', resumeState?.output_format || VIDEO_FORMAT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (AUDIO_FORMAT !== 'auto' && fileType === 'audio') {
|
if ((AUDIO_FORMAT !== 'auto' && fileType === 'audio') || (resumeState?.output_format && fileType === 'audio')) {
|
||||||
args.push('--extract-audio', '--audio-format', AUDIO_FORMAT);
|
args.push('--extract-audio', '--audio-format', resumeState?.output_format || AUDIO_FORMAT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fileType !== 'unknown' && (EMBED_VIDEO_METADATA || EMBED_AUDIO_METADATA)) {
|
let embedMetadata = 0;
|
||||||
if (EMBED_VIDEO_METADATA && (fileType === 'video+audio' || fileType === 'video')) {
|
if (fileType !== 'unknown' && ((EMBED_VIDEO_METADATA || EMBED_AUDIO_METADATA) || resumeState?.embed_metadata)) {
|
||||||
|
if ((EMBED_VIDEO_METADATA || resumeState?.embed_metadata) && (fileType === 'video+audio' || fileType === 'video')) {
|
||||||
|
embedMetadata = 1;
|
||||||
args.push('--embed-metadata');
|
args.push('--embed-metadata');
|
||||||
}
|
}
|
||||||
if (EMBED_AUDIO_METADATA && fileType === 'audio') {
|
if ((EMBED_AUDIO_METADATA || resumeState?.embed_metadata) && fileType === 'audio') {
|
||||||
|
embedMetadata = 1;
|
||||||
args.push('--embed-metadata');
|
args.push('--embed-metadata');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (EMBED_AUDIO_THUMBNAIL && fileType === 'audio') {
|
let embedThumbnail = 0;
|
||||||
|
if (fileType === 'audio' && (EMBED_AUDIO_THUMBNAIL || resumeState?.embed_thumbnail)) {
|
||||||
|
embedThumbnail = 1;
|
||||||
args.push('--embed-thumbnail');
|
args.push('--embed-thumbnail');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (resumeState) {
|
|
||||||
args.push('--continue');
|
|
||||||
} else {
|
|
||||||
args.push('--no-continue');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (USE_PROXY && PROXY_URL) {
|
if (USE_PROXY && PROXY_URL) {
|
||||||
args.push('--proxy', PROXY_URL);
|
args.push('--proxy', PROXY_URL);
|
||||||
}
|
}
|
||||||
@@ -292,6 +301,28 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let sponsorblockRemove = null;
|
||||||
|
let sponsorblockMark = null;
|
||||||
|
if (USE_SPONSORBLOCK || (resumeState?.sponsorblock_remove || resumeState?.sponsorblock_mark)) {
|
||||||
|
if (SPONSORBLOCK_MODE === 'remove' || resumeState?.sponsorblock_remove) {
|
||||||
|
sponsorblockRemove = resumeState?.sponsorblock_remove || (SPONSORBLOCK_REMOVE === 'custom' ? (
|
||||||
|
SPONSORBLOCK_REMOVE_CATEGORIES.length > 0 ? SPONSORBLOCK_REMOVE_CATEGORIES.join(',') : 'default'
|
||||||
|
) : (SPONSORBLOCK_REMOVE));
|
||||||
|
args.push('--sponsorblock-remove', sponsorblockRemove);
|
||||||
|
} else if (SPONSORBLOCK_MODE === 'mark' || resumeState?.sponsorblock_mark) {
|
||||||
|
sponsorblockMark = resumeState?.sponsorblock_mark || (SPONSORBLOCK_MARK === 'custom' ? (
|
||||||
|
SPONSORBLOCK_MARK_CATEGORIES.length > 0 ? SPONSORBLOCK_MARK_CATEGORIES.join(',') : 'default'
|
||||||
|
) : (SPONSORBLOCK_MARK));
|
||||||
|
args.push('--sponsorblock-mark', sponsorblockMark);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (resumeState) {
|
||||||
|
args.push('--continue');
|
||||||
|
} else {
|
||||||
|
args.push('--no-continue');
|
||||||
|
}
|
||||||
|
|
||||||
console.log('Starting download with args:', args);
|
console.log('Starting download with args:', args);
|
||||||
const command = Command.sidecar('binaries/yt-dlp', args);
|
const command = Command.sidecar('binaries/yt-dlp', args);
|
||||||
|
|
||||||
@@ -378,7 +409,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
eta: currentProgress.eta || null,
|
eta: currentProgress.eta || null,
|
||||||
filepath: downloadFilePath,
|
filepath: downloadFilePath,
|
||||||
filetype: determineFileType(videoMetadata.vcodec, videoMetadata.acodec) || null,
|
filetype: determineFileType(videoMetadata.vcodec, videoMetadata.acodec) || null,
|
||||||
filesize: videoMetadata.filesize_approx || null
|
filesize: videoMetadata.filesize_approx || null,
|
||||||
|
output_format: outputFormat,
|
||||||
|
embed_metadata: embedMetadata,
|
||||||
|
embed_thumbnail: embedThumbnail,
|
||||||
|
sponsorblock_remove: sponsorblockRemove,
|
||||||
|
sponsorblock_mark: sponsorblockMark
|
||||||
};
|
};
|
||||||
downloadStateSaver.mutate(state, {
|
downloadStateSaver.mutate(state, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
@@ -463,7 +499,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
|||||||
eta: resumeState?.eta || null,
|
eta: resumeState?.eta || null,
|
||||||
filepath: downloadFilePath,
|
filepath: downloadFilePath,
|
||||||
filetype: resumeState?.filetype || null,
|
filetype: resumeState?.filetype || null,
|
||||||
filesize: resumeState?.filesize || null
|
filesize: resumeState?.filesize || null,
|
||||||
|
output_format: resumeState?.output_format || null,
|
||||||
|
embed_metadata: resumeState?.embed_metadata || 0,
|
||||||
|
embed_thumbnail: resumeState?.embed_thumbnail || 0,
|
||||||
|
sponsorblock_remove: resumeState?.sponsorblock_remove || null,
|
||||||
|
sponsorblock_mark: resumeState?.sponsorblock_mark || null
|
||||||
}
|
}
|
||||||
downloadStateSaver.mutate(state, {
|
downloadStateSaver.mutate(state, {
|
||||||
onSuccess: (data) => {
|
onSuccess: (data) => {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { Switch } from "@/components/ui/switch";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
import { ArrowDownToLine, ArrowRight, BrushCleaning, Cookie, EthernetPort, ExternalLink, FileVideo, Folder, FolderOpen, Info, Loader2, LucideIcon, Monitor, Moon, Radio, RotateCcw, RotateCw, Sun, Terminal, WandSparkles, Wifi, Wrench } from "lucide-react";
|
import { ArrowDownToLine, ArrowRight, BrushCleaning, Cookie, EthernetPort, ExternalLink, FileVideo, Folder, FolderOpen, Info, Loader2, LucideIcon, Monitor, Moon, Radio, RotateCcw, RotateCw, ShieldMinus, Sun, Terminal, WandSparkles, Wifi, Wrench } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
import { cn } from "@/lib/utils";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
import { useTheme } from "@/providers/themeProvider";
|
import { useTheme } from "@/providers/themeProvider";
|
||||||
@@ -27,6 +27,7 @@ import { RadioGroup, RadioGroupItem } from "@/components/ui/radio-group";
|
|||||||
import * as fs from "@tauri-apps/plugin-fs";
|
import * as fs from "@tauri-apps/plugin-fs";
|
||||||
import { join } from "@tauri-apps/api/path";
|
import { join } from "@tauri-apps/api/path";
|
||||||
import { formatSpeed } from "@/utils";
|
import { formatSpeed } from "@/utils";
|
||||||
|
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup";
|
||||||
|
|
||||||
const websocketPortSchema = z.object({
|
const websocketPortSchema = z.object({
|
||||||
port: z.coerce.number<number>({
|
port: z.coerce.number<number>({
|
||||||
@@ -99,6 +100,12 @@ export default function SettingsPage() {
|
|||||||
const importCookiesFrom = useSettingsPageStatesStore(state => state.settings.import_cookies_from);
|
const importCookiesFrom = useSettingsPageStatesStore(state => state.settings.import_cookies_from);
|
||||||
const cookiesBrowser = useSettingsPageStatesStore(state => state.settings.cookies_browser);
|
const cookiesBrowser = useSettingsPageStatesStore(state => state.settings.cookies_browser);
|
||||||
const cookiesFile = useSettingsPageStatesStore(state => state.settings.cookies_file);
|
const cookiesFile = useSettingsPageStatesStore(state => state.settings.cookies_file);
|
||||||
|
const useSponsorblock = useSettingsPageStatesStore(state => state.settings.use_sponsorblock);
|
||||||
|
const sponsorblockMode = useSettingsPageStatesStore(state => state.settings.sponsorblock_mode);
|
||||||
|
const sponsorblockRemove = useSettingsPageStatesStore(state => state.settings.sponsorblock_remove);
|
||||||
|
const sponsorblockMark = useSettingsPageStatesStore(state => state.settings.sponsorblock_mark);
|
||||||
|
const sponsorblockRemoveCategories = useSettingsPageStatesStore(state => state.settings.sponsorblock_remove_categories);
|
||||||
|
const sponsorblockMarkCategories = useSettingsPageStatesStore(state => state.settings.sponsorblock_mark_categories);
|
||||||
|
|
||||||
const websocketPort = useSettingsPageStatesStore(state => state.settings.websocket_port);
|
const websocketPort = useSettingsPageStatesStore(state => state.settings.websocket_port);
|
||||||
const isChangingWebSocketPort = useSettingsPageStatesStore(state => state.isChangingWebSocketPort);
|
const isChangingWebSocketPort = useSettingsPageStatesStore(state => state.isChangingWebSocketPort);
|
||||||
@@ -124,6 +131,19 @@ export default function SettingsPage() {
|
|||||||
{ value: 'system', icon: Monitor, label: 'System' },
|
{ value: 'system', icon: Monitor, label: 'System' },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const sponsorblockCategories = [
|
||||||
|
{ code: 'sponsor', label: 'Sponsorship' },
|
||||||
|
{ code: 'intro', label: 'Intro' },
|
||||||
|
{ code: 'outro', label: 'Outro' },
|
||||||
|
{ code: 'interaction', label: 'Interaction' },
|
||||||
|
{ code: 'selfpromo', label: 'Self Promotion' },
|
||||||
|
{ code: 'music_offtopic', label: 'Music Offtopic' },
|
||||||
|
{ code: 'preview', label: 'Preview' },
|
||||||
|
{ code: 'filler', label: 'Filler' },
|
||||||
|
{ code: 'poi_highlight', label: 'Point of Interest' },
|
||||||
|
{ code: 'chapter', label: 'Chapter' },
|
||||||
|
];
|
||||||
|
|
||||||
const openLink = async (url: string, app: string | null) => {
|
const openLink = async (url: string, app: string | null) => {
|
||||||
try {
|
try {
|
||||||
await invoke('open_file_with_app', { filePath: url, appName: app }).then(() => {
|
await invoke('open_file_with_app', { filePath: url, appName: app }).then(() => {
|
||||||
@@ -394,9 +414,14 @@ export default function SettingsPage() {
|
|||||||
value="cookies"
|
value="cookies"
|
||||||
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground justify-start px-3 py-1.5 gap-2"
|
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground justify-start px-3 py-1.5 gap-2"
|
||||||
><Cookie className="size-4" /> Cookies</TabsTrigger>
|
><Cookie className="size-4" /> Cookies</TabsTrigger>
|
||||||
|
<TabsTrigger
|
||||||
|
key="sponsorblock"
|
||||||
|
value="sponsorblock"
|
||||||
|
className="data-[state=active]:bg-primary data-[state=active]:text-primary-foreground justify-start px-3 py-1.5 gap-2"
|
||||||
|
><ShieldMinus className="size-4" /> Sponsorblock</TabsTrigger>
|
||||||
</TabsList>
|
</TabsList>
|
||||||
<div className="min-h-full flex flex-col max-w-[55%] w-full border-l border-border pl-4">
|
<div className="min-h-full flex flex-col max-w-[55%] w-full border-l border-border pl-4">
|
||||||
<TabsContent key="general" value="general" className="flex flex-col gap-4 min-h-[275px]">
|
<TabsContent key="general" value="general" className="flex flex-col gap-4 min-h-[310px]">
|
||||||
<div className="max-parallel-downloads">
|
<div className="max-parallel-downloads">
|
||||||
<h3 className="font-semibold">Max Parallel Downloads</h3>
|
<h3 className="font-semibold">Max Parallel Downloads</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Set maximum number of allowed parallel downloads</p>
|
<p className="text-xs text-muted-foreground mb-3">Set maximum number of allowed parallel downloads</p>
|
||||||
@@ -442,7 +467,7 @@ export default function SettingsPage() {
|
|||||||
<Label htmlFor="max-retries" className="text-xs text-muted-foreground">(Current: {maxRetries}) (Default: 5, Maximum: 100)</Label>
|
<Label htmlFor="max-retries" className="text-xs text-muted-foreground">(Current: {maxRetries}) (Default: 5, Maximum: 100)</Label>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent key="appearance" value="appearance" className="flex flex-col gap-4 min-h-[275px]">
|
<TabsContent key="appearance" value="appearance" className="flex flex-col gap-4 min-h-[310px]">
|
||||||
<div className="app-theme">
|
<div className="app-theme">
|
||||||
<h3 className="font-semibold">Theme</h3>
|
<h3 className="font-semibold">Theme</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Choose app interface theme</p>
|
<p className="text-xs text-muted-foreground mb-3">Choose app interface theme</p>
|
||||||
@@ -465,7 +490,7 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent key="folders" value="folders" className="flex flex-col gap-4 min-h-[275px]">
|
<TabsContent key="folders" value="folders" className="flex flex-col gap-4 min-h-[310px]">
|
||||||
<div className="download-dir">
|
<div className="download-dir">
|
||||||
<h3 className="font-semibold">Download Folder</h3>
|
<h3 className="font-semibold">Download Folder</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Set default download folder (directory)</p>
|
<p className="text-xs text-muted-foreground mb-3">Set default download folder (directory)</p>
|
||||||
@@ -525,7 +550,7 @@ export default function SettingsPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent key="formats" value="formats" className="flex flex-col gap-4 min-h-[275px]">
|
<TabsContent key="formats" value="formats" className="flex flex-col gap-4 min-h-[310px]">
|
||||||
<div className="video-format">
|
<div className="video-format">
|
||||||
<h3 className="font-semibold">Video Format</h3>
|
<h3 className="font-semibold">Video Format</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Choose in which format the final video file will be saved</p>
|
<p className="text-xs text-muted-foreground mb-3">Choose in which format the final video file will be saved</p>
|
||||||
@@ -590,7 +615,7 @@ export default function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent key="metadata" value="metadata" className="flex flex-col gap-4 min-h-[275px]">
|
<TabsContent key="metadata" value="metadata" className="flex flex-col gap-4 min-h-[310px]">
|
||||||
<div className="embed-video-metadata">
|
<div className="embed-video-metadata">
|
||||||
<h3 className="font-semibold">Embed Metadata</h3>
|
<h3 className="font-semibold">Embed Metadata</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Wheather to embed metadata in video/audio files (info, chapters)</p>
|
<p className="text-xs text-muted-foreground mb-3">Wheather to embed metadata in video/audio files (info, chapters)</p>
|
||||||
@@ -621,7 +646,7 @@ export default function SettingsPage() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent key="network" value="network" className="flex flex-col gap-4 min-h-[275px]">
|
<TabsContent key="network" value="network" className="flex flex-col gap-4 min-h-[310px]">
|
||||||
<div className="proxy">
|
<div className="proxy">
|
||||||
<h3 className="font-semibold">Proxy</h3>
|
<h3 className="font-semibold">Proxy</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Use proxy for downloads, Unblocks blocked sites in your region (download speed may affect, some sites may not work)</p>
|
<p className="text-xs text-muted-foreground mb-3">Use proxy for downloads, Unblocks blocked sites in your region (download speed may affect, some sites may not work)</p>
|
||||||
@@ -703,7 +728,7 @@ export default function SettingsPage() {
|
|||||||
</Form>
|
</Form>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
<TabsContent key="cookies" value="cookies" className="flex flex-col gap-4 min-h-[275px]">
|
<TabsContent key="cookies" value="cookies" className="flex flex-col gap-4 min-h-[310px]">
|
||||||
<div className="cookies">
|
<div className="cookies">
|
||||||
<h3 className="font-semibold">Cookies</h3>
|
<h3 className="font-semibold">Cookies</h3>
|
||||||
<p className="text-xs text-muted-foreground mb-3">Use cookies to access exclusive/private (login-protected) contents from sites (use wisely, over-use can even block/ban your account)</p>
|
<p className="text-xs text-muted-foreground mb-3">Use cookies to access exclusive/private (login-protected) contents from sites (use wisely, over-use can even block/ban your account)</p>
|
||||||
@@ -788,6 +813,130 @@ export default function SettingsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<Label className="text-xs text-muted-foreground">(Configured: {importCookiesFrom === "browser" ? 'Yes' : cookiesFile ? 'Yes' : 'No'}, From: {importCookiesFrom === "browser" ? 'Browser' : 'Text'}, Status: {useCookies ? 'Enabled' : 'Disabled'})</Label>
|
||||||
|
</div>
|
||||||
|
</TabsContent>
|
||||||
|
<TabsContent key="sponsorblock" value="sponsorblock" className="flex flex-col gap-4 min-h-[310px]">
|
||||||
|
<div className="sponsorblock">
|
||||||
|
<h3 className="font-semibold">Sponsor Block</h3>
|
||||||
|
<p className="text-xs text-muted-foreground mb-3">Use sponsorblock to remove/mark unwanted segments in videos (sponsorships, intros, outros, etc.)</p>
|
||||||
|
<div className="flex items-center space-x-2 mb-4">
|
||||||
|
<Switch
|
||||||
|
id="use-sponsorblock"
|
||||||
|
checked={useSponsorblock}
|
||||||
|
onCheckedChange={(checked) => saveSettingsKey('use_sponsorblock', checked)}
|
||||||
|
/>
|
||||||
|
<Label htmlFor="use-sponsorblock">Use Sponsorblock</Label>
|
||||||
|
</div>
|
||||||
|
<RadioGroup
|
||||||
|
orientation="horizontal"
|
||||||
|
className="flex items-center gap-4"
|
||||||
|
value={sponsorblockMode}
|
||||||
|
onValueChange={(value) => saveSettingsKey('sponsorblock_mode', value)}
|
||||||
|
disabled={!useSponsorblock}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="remove" id="sponsorblock-remove" />
|
||||||
|
<Label htmlFor="sponsorblock-remove">Remove Segments</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="mark" id="sponsorblock-mark" />
|
||||||
|
<Label htmlFor="sponsorblock-mark">Mark Segments</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
<div className="flex flex-col gap-2 mt-5">
|
||||||
|
<Label className="text-xs mb-1">Sponsorblock Remove Categories</Label>
|
||||||
|
<RadioGroup
|
||||||
|
orientation="horizontal"
|
||||||
|
className="flex items-center gap-4"
|
||||||
|
value={sponsorblockRemove}
|
||||||
|
onValueChange={(value) => saveSettingsKey('sponsorblock_remove', value)}
|
||||||
|
disabled={!useSponsorblock || sponsorblockMode !== "remove"}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="default" id="sponsorblock-remove-default" />
|
||||||
|
<Label htmlFor="sponsorblock-remove-default">Default</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="all" id="sponsorblock-remove-all" />
|
||||||
|
<Label htmlFor="sponsorblock-remove-all">All</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="custom" id="sponsorblock-remove-custom" />
|
||||||
|
<Label htmlFor="sponsorblock-remove-custom">Custom</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
<ToggleGroup
|
||||||
|
type="multiple"
|
||||||
|
variant="outline"
|
||||||
|
className="flex flex-col items-start gap-2 mt-1"
|
||||||
|
value={sponsorblockRemove === "custom" ? sponsorblockRemoveCategories : sponsorblockRemove === "default" ? sponsorblockCategories.filter((cat) => cat.code !== 'poi_highlight' && cat.code !== 'filler').map((cat) => cat.code) : sponsorblockRemove === "all" ? sponsorblockCategories.filter((cat) => cat.code !== 'poi_highlight').map((cat) => cat.code) : []}
|
||||||
|
onValueChange={(value) => saveSettingsKey('sponsorblock_remove_categories', value)}
|
||||||
|
disabled={!useSponsorblock || sponsorblockMode !== "remove" || sponsorblockRemove !== "custom"}
|
||||||
|
>
|
||||||
|
<div className="flex gap-2 flex-wrap items-center">
|
||||||
|
{sponsorblockCategories.map((category) => (
|
||||||
|
category.code !== "poi_highlight" && (
|
||||||
|
<ToggleGroupItem
|
||||||
|
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-muted/70 hover:bg-muted/70"
|
||||||
|
value={category.code}
|
||||||
|
size="sm"
|
||||||
|
aria-label={category.label}
|
||||||
|
key={category.code}
|
||||||
|
>
|
||||||
|
{category.label}
|
||||||
|
</ToggleGroupItem>
|
||||||
|
)
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-col gap-2 mt-4">
|
||||||
|
<Label className="text-xs mb-1">Sponsorblock Mark Categories</Label>
|
||||||
|
<RadioGroup
|
||||||
|
orientation="horizontal"
|
||||||
|
className="flex items-center gap-4"
|
||||||
|
value={sponsorblockMark}
|
||||||
|
onValueChange={(value) => saveSettingsKey('sponsorblock_mark', value)}
|
||||||
|
disabled={!useSponsorblock || sponsorblockMode !== "mark"}
|
||||||
|
>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="default" id="sponsorblock-mark-default" />
|
||||||
|
<Label htmlFor="sponsorblock-mark-default">Default</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="all" id="sponsorblock-mark-all" />
|
||||||
|
<Label htmlFor="sponsorblock-mark-all">All</Label>
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center gap-3">
|
||||||
|
<RadioGroupItem value="custom" id="sponsorblock-mark-custom" />
|
||||||
|
<Label htmlFor="sponsorblock-mark-custom">Custom</Label>
|
||||||
|
</div>
|
||||||
|
</RadioGroup>
|
||||||
|
<ToggleGroup
|
||||||
|
type="multiple"
|
||||||
|
variant="outline"
|
||||||
|
className="flex flex-col items-start gap-2 mt-1 mb-2"
|
||||||
|
value={sponsorblockMark === "custom" ? sponsorblockMarkCategories : sponsorblockMark === "default" ? sponsorblockCategories.map((cat) => cat.code) : sponsorblockMark === "all" ? sponsorblockCategories.map((cat) => cat.code) : []}
|
||||||
|
onValueChange={(value) => saveSettingsKey('sponsorblock_mark_categories', value)}
|
||||||
|
disabled={!useSponsorblock || sponsorblockMode !== "mark" || sponsorblockMark !== "custom"}
|
||||||
|
>
|
||||||
|
<div className="flex gap-2 flex-wrap items-center">
|
||||||
|
{sponsorblockCategories.map((category) => (
|
||||||
|
<ToggleGroupItem
|
||||||
|
className="text-xs text-nowrap border-2 data-[state=on]:border-2 data-[state=on]:border-primary data-[state=on]:bg-muted/70 hover:bg-muted/70"
|
||||||
|
value={category.code}
|
||||||
|
size="sm"
|
||||||
|
aria-label={category.label}
|
||||||
|
key={category.code}
|
||||||
|
>
|
||||||
|
{category.label}
|
||||||
|
</ToggleGroupItem>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</ToggleGroup>
|
||||||
|
</div>
|
||||||
|
<Label className="text-xs text-muted-foreground">(Configured: {sponsorblockMode === "remove" && sponsorblockRemove === "custom" && sponsorblockRemoveCategories.length <= 0 ? 'No' : sponsorblockMode === "mark" && sponsorblockMark === "custom" && sponsorblockMarkCategories.length <= 0 ? 'No' : 'Yes'}, Mode: {sponsorblockMode === "remove" ? 'Remove' : 'Mark'}, Status: {useSponsorblock ? 'Enabled' : 'Disabled'})</Label>
|
||||||
</div>
|
</div>
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -196,7 +196,12 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
|||||||
eta = $22,
|
eta = $22,
|
||||||
filepath = $23,
|
filepath = $23,
|
||||||
filetype = $24,
|
filetype = $24,
|
||||||
filesize = $25
|
filesize = $25,
|
||||||
|
output_format = $26,
|
||||||
|
embed_metadata = $27,
|
||||||
|
embed_thumbnail = $28,
|
||||||
|
sponsorblock_remove = $29,
|
||||||
|
sponsorblock_mark = $30
|
||||||
WHERE download_id = $1`,
|
WHERE download_id = $1`,
|
||||||
[
|
[
|
||||||
downloadState.download_id,
|
downloadState.download_id,
|
||||||
@@ -223,7 +228,12 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
|||||||
downloadState.eta,
|
downloadState.eta,
|
||||||
downloadState.filepath,
|
downloadState.filepath,
|
||||||
downloadState.filetype,
|
downloadState.filetype,
|
||||||
downloadState.filesize
|
downloadState.filesize,
|
||||||
|
downloadState.output_format,
|
||||||
|
downloadState.embed_metadata,
|
||||||
|
downloadState.embed_thumbnail,
|
||||||
|
downloadState.sponsorblock_remove,
|
||||||
|
downloadState.sponsorblock_mark
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -252,8 +262,13 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
|||||||
eta,
|
eta,
|
||||||
filepath,
|
filepath,
|
||||||
filetype,
|
filetype,
|
||||||
filesize
|
filesize,
|
||||||
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25)`,
|
output_format,
|
||||||
|
embed_metadata,
|
||||||
|
embed_thumbnail,
|
||||||
|
sponsorblock_remove,
|
||||||
|
sponsorblock_mark
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15, $16, $17, $18, $19, $20, $21, $22, $23, $24, $25, $26, $27, $28, $29, $30)`,
|
||||||
[
|
[
|
||||||
downloadState.download_id,
|
downloadState.download_id,
|
||||||
downloadState.download_status,
|
downloadState.download_status,
|
||||||
@@ -279,7 +294,12 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
|||||||
downloadState.eta,
|
downloadState.eta,
|
||||||
downloadState.filepath,
|
downloadState.filepath,
|
||||||
downloadState.filetype,
|
downloadState.filetype,
|
||||||
downloadState.filesize
|
downloadState.filesize,
|
||||||
|
downloadState.output_format,
|
||||||
|
downloadState.embed_metadata,
|
||||||
|
downloadState.embed_thumbnail,
|
||||||
|
downloadState.sponsorblock_remove,
|
||||||
|
downloadState.sponsorblock_mark
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -145,6 +145,12 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
|
|||||||
import_cookies_from: 'browser',
|
import_cookies_from: 'browser',
|
||||||
cookies_browser: 'firefox',
|
cookies_browser: 'firefox',
|
||||||
cookies_file: '',
|
cookies_file: '',
|
||||||
|
use_sponsorblock: false,
|
||||||
|
sponsorblock_mode: 'remove',
|
||||||
|
sponsorblock_remove: 'default',
|
||||||
|
sponsorblock_mark: 'default',
|
||||||
|
sponsorblock_remove_categories: [],
|
||||||
|
sponsorblock_mark_categories: [],
|
||||||
// extension settings
|
// extension settings
|
||||||
websocket_port: 53511
|
websocket_port: 53511
|
||||||
},
|
},
|
||||||
@@ -194,6 +200,12 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
|
|||||||
import_cookies_from: 'browser',
|
import_cookies_from: 'browser',
|
||||||
cookies_browser: 'firefox',
|
cookies_browser: 'firefox',
|
||||||
cookies_file: '',
|
cookies_file: '',
|
||||||
|
use_sponsorblock: false,
|
||||||
|
sponsorblock_mode: 'remove',
|
||||||
|
sponsorblock_remove: 'default',
|
||||||
|
sponsorblock_mark: 'default',
|
||||||
|
sponsorblock_remove_categories: [],
|
||||||
|
sponsorblock_mark_categories: [],
|
||||||
// extension settings
|
// extension settings
|
||||||
websocket_port: 53511
|
websocket_port: 53511
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -37,6 +37,13 @@ export interface DownloadState {
|
|||||||
filepath: string | null;
|
filepath: string | null;
|
||||||
filetype: string | null;
|
filetype: string | null;
|
||||||
filesize: number | null;
|
filesize: number | null;
|
||||||
|
output_format: string | null;
|
||||||
|
embed_metadata: number;
|
||||||
|
embed_thumbnail: number;
|
||||||
|
sponsorblock_remove: string | null;
|
||||||
|
sponsorblock_mark: string | null;
|
||||||
|
created_at?: string;
|
||||||
|
updated_at?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface Download {
|
export interface Download {
|
||||||
@@ -65,6 +72,13 @@ export interface Download {
|
|||||||
filepath: string | null;
|
filepath: string | null;
|
||||||
filetype: string | null;
|
filetype: string | null;
|
||||||
filesize: number | null;
|
filesize: number | null;
|
||||||
|
output_format: string | null;
|
||||||
|
embed_metadata: number;
|
||||||
|
embed_thumbnail: number;
|
||||||
|
sponsorblock_remove: string | null;
|
||||||
|
sponsorblock_mark: string | null;
|
||||||
|
created_at: string;
|
||||||
|
updated_at: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DownloadProgress {
|
export interface DownloadProgress {
|
||||||
|
|||||||
@@ -26,6 +26,12 @@ export interface Settings {
|
|||||||
import_cookies_from: string;
|
import_cookies_from: string;
|
||||||
cookies_browser: string;
|
cookies_browser: string;
|
||||||
cookies_file: string;
|
cookies_file: string;
|
||||||
|
use_sponsorblock: boolean;
|
||||||
|
sponsorblock_mode: string;
|
||||||
|
sponsorblock_remove: string;
|
||||||
|
sponsorblock_mark: string;
|
||||||
|
sponsorblock_remove_categories: string[];
|
||||||
|
sponsorblock_mark_categories: string[];
|
||||||
// extension settings
|
// extension settings
|
||||||
websocket_port: number;
|
websocket_port: number;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user