mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-19 01:32:57 +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,
|
||||
},
|
||||
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": {
|
||||
"sql": {
|
||||
"preload": ["sqlite:database.db"]
|
||||
},
|
||||
"updater": {
|
||||
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDNDM0I4ODcyODdGOTM4MDIKUldRQ09QbUhjb2c3UENGY1lFUVdTVWhucmJ4QzdGeW9sU3VHVFlGNWY5anZab2s4SU1rMWFsekMK",
|
||||
"endpoints": [
|
||||
|
||||
89
src/App.tsx
89
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 COOKIES_BROWSER = useSettingsPageStatesStore(state => state.settings.cookies_browser);
|
||||
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 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 (resumeState && resumeState.output_format) videoMetadata.ext = resumeState.output_format;
|
||||
|
||||
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 downloadId = resumeState?.download_id || generateDownloadId(videoMetadata.id, videoMetadata.webpage_url_domain);
|
||||
@@ -237,53 +245,54 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
args.push('--playlist-items', playlistIndex);
|
||||
}
|
||||
|
||||
if (fileType !== 'unknown' && (VIDEO_FORMAT !== 'auto' || AUDIO_FORMAT !== 'auto')) {
|
||||
if (VIDEO_FORMAT !== 'auto' && fileType === 'video+audio') {
|
||||
let outputFormat = null;
|
||||
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) {
|
||||
args.push('--recode-video', VIDEO_FORMAT);
|
||||
args.push('--recode-video', resumeState?.output_format || VIDEO_FORMAT);
|
||||
} 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) {
|
||||
args.push('--recode-video', VIDEO_FORMAT);
|
||||
args.push('--recode-video', resumeState?.output_format || VIDEO_FORMAT);
|
||||
} else {
|
||||
args.push('--remux-video', VIDEO_FORMAT);
|
||||
args.push('--remux-video', resumeState?.output_format || VIDEO_FORMAT);
|
||||
}
|
||||
}
|
||||
if (AUDIO_FORMAT !== 'auto' && fileType === 'audio') {
|
||||
args.push('--extract-audio', '--audio-format', AUDIO_FORMAT);
|
||||
if ((AUDIO_FORMAT !== 'auto' && fileType === 'audio') || (resumeState?.output_format && fileType === 'audio')) {
|
||||
args.push('--extract-audio', '--audio-format', resumeState?.output_format || AUDIO_FORMAT);
|
||||
}
|
||||
}
|
||||
|
||||
if (fileType !== 'unknown' && (EMBED_VIDEO_METADATA || EMBED_AUDIO_METADATA)) {
|
||||
if (EMBED_VIDEO_METADATA && (fileType === 'video+audio' || fileType === 'video')) {
|
||||
let embedMetadata = 0;
|
||||
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');
|
||||
}
|
||||
if (EMBED_AUDIO_METADATA && fileType === 'audio') {
|
||||
if ((EMBED_AUDIO_METADATA || resumeState?.embed_metadata) && fileType === 'audio') {
|
||||
embedMetadata = 1;
|
||||
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');
|
||||
}
|
||||
|
||||
if (resumeState) {
|
||||
args.push('--continue');
|
||||
} else {
|
||||
args.push('--no-continue');
|
||||
}
|
||||
|
||||
|
||||
if (USE_PROXY && PROXY_URL) {
|
||||
args.push('--proxy', PROXY_URL);
|
||||
}
|
||||
|
||||
|
||||
if (USE_RATE_LIMIT && RATE_LIMIT) {
|
||||
args.push('--limit-rate', `${RATE_LIMIT}`);
|
||||
}
|
||||
|
||||
|
||||
if (USE_COOKIES) {
|
||||
if (IMPORT_COOKIES_FROM === 'browser' && COOKIES_BROWSER) {
|
||||
args.push('--cookies-from-browser', COOKIES_BROWSER);
|
||||
@@ -291,6 +300,28 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
args.push('--cookies', COOKIES_FILE);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
const command = Command.sidecar('binaries/yt-dlp', args);
|
||||
@@ -378,7 +409,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
eta: currentProgress.eta || null,
|
||||
filepath: downloadFilePath,
|
||||
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, {
|
||||
onSuccess: (data) => {
|
||||
@@ -463,7 +499,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
eta: resumeState?.eta || null,
|
||||
filepath: downloadFilePath,
|
||||
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, {
|
||||
onSuccess: (data) => {
|
||||
|
||||
@@ -7,7 +7,7 @@ import { Switch } from "@/components/ui/switch";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Button } from "@/components/ui/button";
|
||||
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 { useEffect } from "react";
|
||||
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 { join } from "@tauri-apps/api/path";
|
||||
import { formatSpeed } from "@/utils";
|
||||
import { ToggleGroup, ToggleGroupItem } from "@/components/custom/legacyToggleGroup";
|
||||
|
||||
const websocketPortSchema = z.object({
|
||||
port: z.coerce.number<number>({
|
||||
@@ -99,6 +100,12 @@ export default function SettingsPage() {
|
||||
const importCookiesFrom = useSettingsPageStatesStore(state => state.settings.import_cookies_from);
|
||||
const cookiesBrowser = useSettingsPageStatesStore(state => state.settings.cookies_browser);
|
||||
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 isChangingWebSocketPort = useSettingsPageStatesStore(state => state.isChangingWebSocketPort);
|
||||
@@ -124,6 +131,19 @@ export default function SettingsPage() {
|
||||
{ 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) => {
|
||||
try {
|
||||
await invoke('open_file_with_app', { filePath: url, appName: app }).then(() => {
|
||||
@@ -394,9 +414,14 @@ export default function SettingsPage() {
|
||||
value="cookies"
|
||||
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>
|
||||
<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>
|
||||
<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">
|
||||
<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>
|
||||
@@ -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>
|
||||
</div>
|
||||
</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">
|
||||
<h3 className="font-semibold">Theme</h3>
|
||||
<p className="text-xs text-muted-foreground mb-3">Choose app interface theme</p>
|
||||
@@ -465,7 +490,7 @@ export default function SettingsPage() {
|
||||
</div>
|
||||
</div>
|
||||
</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">
|
||||
<h3 className="font-semibold">Download Folder</h3>
|
||||
<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>
|
||||
</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">
|
||||
<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>
|
||||
@@ -590,7 +615,7 @@ export default function SettingsPage() {
|
||||
/>
|
||||
</div>
|
||||
</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">
|
||||
<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>
|
||||
@@ -621,7 +646,7 @@ export default function SettingsPage() {
|
||||
/>
|
||||
</div>
|
||||
</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">
|
||||
<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>
|
||||
@@ -703,7 +728,7 @@ export default function SettingsPage() {
|
||||
</Form>
|
||||
</div>
|
||||
</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">
|
||||
<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>
|
||||
@@ -788,6 +813,130 @@ export default function SettingsPage() {
|
||||
</Button>
|
||||
</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>
|
||||
</TabsContent>
|
||||
</div>
|
||||
|
||||
@@ -196,7 +196,12 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
eta = $22,
|
||||
filepath = $23,
|
||||
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`,
|
||||
[
|
||||
downloadState.download_id,
|
||||
@@ -223,7 +228,12 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
downloadState.eta,
|
||||
downloadState.filepath,
|
||||
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,
|
||||
filepath,
|
||||
filetype,
|
||||
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)`,
|
||||
filesize,
|
||||
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_status,
|
||||
@@ -279,7 +294,12 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
downloadState.eta,
|
||||
downloadState.filepath,
|
||||
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',
|
||||
cookies_browser: 'firefox',
|
||||
cookies_file: '',
|
||||
use_sponsorblock: false,
|
||||
sponsorblock_mode: 'remove',
|
||||
sponsorblock_remove: 'default',
|
||||
sponsorblock_mark: 'default',
|
||||
sponsorblock_remove_categories: [],
|
||||
sponsorblock_mark_categories: [],
|
||||
// extension settings
|
||||
websocket_port: 53511
|
||||
},
|
||||
@@ -194,6 +200,12 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
|
||||
import_cookies_from: 'browser',
|
||||
cookies_browser: 'firefox',
|
||||
cookies_file: '',
|
||||
use_sponsorblock: false,
|
||||
sponsorblock_mode: 'remove',
|
||||
sponsorblock_remove: 'default',
|
||||
sponsorblock_mark: 'default',
|
||||
sponsorblock_remove_categories: [],
|
||||
sponsorblock_mark_categories: [],
|
||||
// extension settings
|
||||
websocket_port: 53511
|
||||
},
|
||||
|
||||
@@ -37,6 +37,13 @@ export interface DownloadState {
|
||||
filepath: string | null;
|
||||
filetype: string | 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 {
|
||||
@@ -65,6 +72,13 @@ export interface Download {
|
||||
filepath: string | null;
|
||||
filetype: string | 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 {
|
||||
|
||||
@@ -26,6 +26,12 @@ export interface Settings {
|
||||
import_cookies_from: string;
|
||||
cookies_browser: 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
|
||||
websocket_port: number;
|
||||
}
|
||||
Reference in New Issue
Block a user