feat: added sponsorblock support and improved resume persistence

This commit is contained in:
2025-08-26 09:14:49 +05:30
parent 4184b4b779
commit b73ab86066
8 changed files with 319 additions and 37 deletions

View File

@@ -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,
}]
}

View File

@@ -36,6 +36,9 @@
]
},
"plugins": {
"sql": {
"preload": ["sqlite:database.db"]
},
"updater": {
"pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDNDM0I4ODcyODdGOTM4MDIKUldRQ09QbUhjb2c3UENGY1lFUVdTVWhucmJ4QzdGeW9sU3VHVFlGNWY5anZab2s4SU1rMWFsekMK",
"endpoints": [

View File

@@ -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) => {

View File

@@ -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>

View File

@@ -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
]
)
}

View File

@@ -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
},

View File

@@ -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 {

View File

@@ -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;
}