mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-19 03:52:58 +05:30
refactor: switched to yt-dlp way of path resolution, optimized database migrations and improved queuing
This commit is contained in:
28
package-lock.json
generated
28
package-lock.json
generated
@@ -64,6 +64,7 @@
|
||||
"recharts": "^3.1.2",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"ulid": "^3.0.1",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^4.1.0",
|
||||
"zustand": "^5.0.8"
|
||||
@@ -141,6 +142,7 @@
|
||||
"integrity": "sha512-yDBHV9kQNcr2/sUr9jghVyz9C3Y5G2zUM2H2lo+9mKv4sFgbA8s8Z9t8D1jiTkGoO/NoIfKMyKWr4s6CN23ZwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@ampproject/remapping": "^2.2.0",
|
||||
"@babel/code-frame": "^7.27.1",
|
||||
@@ -2948,6 +2950,7 @@
|
||||
"resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.85.5.tgz",
|
||||
"integrity": "sha512-/X4EFNcnPiSs8wM2v+b6DqS5mmGeuJQvxBglmDxl6ZQb5V26ouD2SJYAcC3VjbNwqhY2zjxVD15rDA5nGbMn3A==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@tanstack/query-core": "5.85.5"
|
||||
},
|
||||
@@ -3396,6 +3399,7 @@
|
||||
"integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~7.10.0"
|
||||
}
|
||||
@@ -3406,6 +3410,7 @@
|
||||
"integrity": "sha512-lr3jdBw/BGj49Eps7EvqlUaoeA0xpj3pc0RoJkHpYaCHkVK7i28dKyImLQb3JVlqs3aYSXf7qYuWOW/fgZnTXQ==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"csstype": "^3.0.2"
|
||||
}
|
||||
@@ -3416,6 +3421,7 @@
|
||||
"integrity": "sha512-i5ZzwYpqjmrKenzkoLM2Ibzt6mAsM7pxB6BCIouEVVmgiqaMj1TjaK7hnA36hbW5aZv20kx7Lw6hWzPWg0Rurw==",
|
||||
"devOptional": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"peerDependencies": {
|
||||
"@types/react": "^19.0.0"
|
||||
}
|
||||
@@ -3479,6 +3485,7 @@
|
||||
}
|
||||
],
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"caniuse-lite": "^1.0.30001726",
|
||||
"electron-to-chromium": "^1.5.173",
|
||||
@@ -3771,7 +3778,8 @@
|
||||
"version": "8.6.0",
|
||||
"resolved": "https://registry.npmjs.org/embla-carousel/-/embla-carousel-8.6.0.tgz",
|
||||
"integrity": "sha512-SjWyZBHJPbqxHOzckOfo8lHisEaJWmwd23XppYFYVh10bU66/Pn5tkVkbkCMZVdbUE5eTCI2nD8OyIP4Z+uwkA==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/embla-carousel-react": {
|
||||
"version": "8.6.0",
|
||||
@@ -4371,6 +4379,7 @@
|
||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
@@ -4412,6 +4421,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react/-/react-19.1.1.tgz",
|
||||
"integrity": "sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -4442,6 +4452,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.1.tgz",
|
||||
"integrity": "sha512-Dlq/5LAZgF0Gaz6yiqZCf6VCcZs1ghAJyrsu84Q/GT0gV+mCxbfmKNoGRKBYMJ8IEdGPqu49YWXD02GCknEDkw==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"scheduler": "^0.26.0"
|
||||
},
|
||||
@@ -4454,6 +4465,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.62.0.tgz",
|
||||
"integrity": "sha512-7KWFejc98xqG/F4bAxpL41NB3o1nnvQO1RWZT3TqRZYL8RryQETGfEdVnJN2fy1crCiBLLjkRBVK05j24FxJGA==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
},
|
||||
@@ -4477,6 +4489,7 @@
|
||||
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz",
|
||||
"integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==",
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"@types/use-sync-external-store": "^0.0.6",
|
||||
"use-sync-external-store": "^1.4.0"
|
||||
@@ -4653,7 +4666,8 @@
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz",
|
||||
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
||||
"license": "MIT"
|
||||
"license": "MIT",
|
||||
"peer": true
|
||||
},
|
||||
"node_modules/redux-thunk": {
|
||||
"version": "3.1.0",
|
||||
@@ -4864,6 +4878,15 @@
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/ulid": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ulid/-/ulid-3.0.1.tgz",
|
||||
"integrity": "sha512-dPJyqPzx8preQhqq24bBG1YNkvigm87K8kVEHCD+ruZg24t6IFEFv00xMWfxcC4djmFtiTLdFuADn4+DOz6R7Q==",
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"ulid": "dist/cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "7.10.0",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz",
|
||||
@@ -4995,6 +5018,7 @@
|
||||
"integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"peer": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.25.0",
|
||||
"fdir": "^6.5.0",
|
||||
|
||||
@@ -66,6 +66,7 @@
|
||||
"recharts": "^3.1.2",
|
||||
"sonner": "^2.0.7",
|
||||
"tailwind-merge": "^3.3.1",
|
||||
"ulid": "^3.0.1",
|
||||
"vaul": "^1.1.2",
|
||||
"zod": "^4.1.0",
|
||||
"zustand": "^5.0.8"
|
||||
|
||||
@@ -73,119 +73,7 @@ pub fn get_migrations() -> Vec<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,
|
||||
},
|
||||
Migration {
|
||||
version: 3,
|
||||
description: "add_use_aria2_column_to_downloads_with_proper_position",
|
||||
sql: "
|
||||
-- Create temporary table with the new column in the correct position
|
||||
CREATE TABLE downloads_temp (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
download_id TEXT UNIQUE NOT NULL,
|
||||
download_status TEXT NOT NULL,
|
||||
video_id TEXT NOT NULL,
|
||||
format_id TEXT NOT NULL,
|
||||
subtitle_id TEXT,
|
||||
queue_index INTEGER,
|
||||
playlist_id TEXT,
|
||||
playlist_index INTEGER,
|
||||
resolution TEXT,
|
||||
ext TEXT,
|
||||
abr REAL,
|
||||
vbr REAL,
|
||||
acodec TEXT,
|
||||
vcodec TEXT,
|
||||
dynamic_range TEXT,
|
||||
process_id INTEGER,
|
||||
status TEXT,
|
||||
progress REAL,
|
||||
total INTEGER,
|
||||
downloaded INTEGER,
|
||||
speed REAL,
|
||||
eta INTEGER,
|
||||
filepath TEXT,
|
||||
filetype TEXT,
|
||||
filesize INTEGER,
|
||||
output_format TEXT,
|
||||
embed_metadata INTEGER NOT NULL DEFAULT 0,
|
||||
embed_thumbnail INTEGER NOT NULL DEFAULT 0,
|
||||
sponsorblock_remove TEXT,
|
||||
sponsorblock_mark TEXT,
|
||||
use_aria2 INTEGER NOT NULL DEFAULT 0,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (video_id) REFERENCES video_info (video_id),
|
||||
FOREIGN KEY (playlist_id) REFERENCES playlist_info (playlist_id)
|
||||
);
|
||||
|
||||
-- Copy all data from original table to temporary table
|
||||
INSERT INTO downloads_temp SELECT
|
||||
id, download_id, download_status, video_id, format_id, subtitle_id,
|
||||
queue_index, playlist_id, playlist_index, resolution, ext, abr, vbr,
|
||||
acodec, vcodec, dynamic_range, process_id, status, progress, total,
|
||||
downloaded, speed, eta, filepath, filetype, filesize, output_format,
|
||||
embed_metadata, embed_thumbnail, sponsorblock_remove, sponsorblock_mark,
|
||||
0, -- use_aria2 default value
|
||||
created_at, updated_at
|
||||
FROM downloads;
|
||||
|
||||
-- Drop existing triggers for the original table
|
||||
DROP TRIGGER IF EXISTS update_downloads_updated_at;
|
||||
DROP TRIGGER IF EXISTS set_downloads_timestamps;
|
||||
|
||||
-- Drop the original table
|
||||
DROP TABLE downloads;
|
||||
|
||||
-- Rename temporary table to original name
|
||||
ALTER TABLE downloads_temp RENAME TO downloads;
|
||||
|
||||
-- Create only the update trigger (as insert trigger not needed anymore)
|
||||
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;
|
||||
",
|
||||
kind: MigrationKind::Up,
|
||||
},
|
||||
Migration {
|
||||
version: 4,
|
||||
description: "add_custom_command_column_to_downloads",
|
||||
sql: "
|
||||
-- Create temporary table with the new column in the correct position
|
||||
-- Create temporary table with all new columns
|
||||
CREATE TABLE downloads_temp (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
download_id TEXT UNIQUE NOT NULL,
|
||||
@@ -220,33 +108,38 @@ pub fn get_migrations() -> Vec<Migration> {
|
||||
sponsorblock_mark TEXT,
|
||||
use_aria2 INTEGER NOT NULL DEFAULT 0,
|
||||
custom_command TEXT,
|
||||
queue_config TEXT,
|
||||
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (video_id) REFERENCES video_info (video_id),
|
||||
FOREIGN KEY (playlist_id) REFERENCES playlist_info (playlist_id)
|
||||
);
|
||||
|
||||
-- Copy all data from original table to temporary table
|
||||
INSERT INTO downloads_temp SELECT
|
||||
|
||||
-- Copy all data from original table to temporary table with default values for new columns
|
||||
INSERT INTO downloads_temp SELECT
|
||||
id, download_id, download_status, video_id, format_id, subtitle_id,
|
||||
queue_index, playlist_id, playlist_index, resolution, ext, abr, vbr,
|
||||
acodec, vcodec, dynamic_range, process_id, status, progress, total,
|
||||
downloaded, speed, eta, filepath, filetype, filesize, output_format,
|
||||
embed_metadata, embed_thumbnail, sponsorblock_remove, sponsorblock_mark,
|
||||
use_aria2, NULL, -- custom_command default value
|
||||
created_at, updated_at
|
||||
downloaded, speed, eta, filepath, filetype, filesize,
|
||||
NULL, -- output_format
|
||||
0, -- embed_metadata
|
||||
0, -- embed_thumbnail
|
||||
NULL, -- sponsorblock_remove
|
||||
NULL, -- sponsorblock_mark
|
||||
0, -- use_aria2
|
||||
NULL, -- custom_command
|
||||
NULL, -- queue_config
|
||||
CURRENT_TIMESTAMP, -- created_at
|
||||
CURRENT_TIMESTAMP -- updated_at
|
||||
FROM downloads;
|
||||
|
||||
-- Drop existing triggers for the original table
|
||||
DROP TRIGGER IF EXISTS update_downloads_updated_at;
|
||||
|
||||
-- Drop the original table
|
||||
DROP TABLE downloads;
|
||||
|
||||
|
||||
-- Rename temporary table to original name
|
||||
ALTER TABLE downloads_temp RENAME TO downloads;
|
||||
|
||||
-- Re-Create the update trigger
|
||||
|
||||
-- Create trigger for updating updated_at timestamp
|
||||
CREATE TRIGGER IF NOT EXISTS update_downloads_updated_at
|
||||
AFTER UPDATE ON downloads
|
||||
FOR EACH ROW
|
||||
|
||||
140
src/App.tsx
140
src/App.tsx
@@ -7,7 +7,7 @@ import { useCallback, useEffect, useRef, useState } from "react";
|
||||
import { arch, exeExtension } from "@tauri-apps/plugin-os";
|
||||
import { downloadDir, join, resourceDir, tempDir } from "@tauri-apps/api/path";
|
||||
import { useBasePathsStore, useCurrentVideoMetadataStore, useDownloaderPageStatesStore, useDownloadStatesStore, useKvPairsStatesStore, useSettingsPageStatesStore } from "@/services/store";
|
||||
import { determineFileType, generateDownloadId, generateSafeFilePath, generateVideoId, isObjEmpty, parseProgressLine, sanitizeFilename } from "@/utils";
|
||||
import { determineFileType, generateVideoId, isObjEmpty, parseProgressLine } from "@/utils";
|
||||
import { Command } from "@tauri-apps/plugin-shell";
|
||||
import { RawVideoInfo } from "@/types/video";
|
||||
import { useDeleteDownloadState, useSaveDownloadState, useSavePlaylistInfo, useSaveVideoInfo, useUpdateDownloadFilePath, useUpdateDownloadStatus } from "@/services/mutations";
|
||||
@@ -27,7 +27,8 @@ import useAppUpdater from "@/helpers/use-app-updater";
|
||||
import { Toaster as Sonner } from "@/components/ui/sonner";
|
||||
import { toast } from "sonner";
|
||||
import { useLogger } from "@/helpers/use-logger";
|
||||
import { DownloadConfiguration } from "./types/settings";
|
||||
import { DownloadConfiguration } from "@/types/settings";
|
||||
import { ulid } from "ulid";
|
||||
|
||||
export default function App({ children }: { children: React.ReactNode }) {
|
||||
const { data: downloadStates, isSuccess: isSuccessFetchingDownloadStates } = useFetchAllDownloadStates();
|
||||
@@ -45,7 +46,6 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const setSearchPid = useCurrentVideoMetadataStore((state) => state.setSearchPid);
|
||||
|
||||
// const isUsingDefaultSettings = useSettingsPageStatesStore((state) => state.isUsingDefaultSettings);
|
||||
const setIsUsingDefaultSettings = useSettingsPageStatesStore((state) => state.setIsUsingDefaultSettings);
|
||||
const setSettingsKey = useSettingsPageStatesStore((state) => state.setSettingsKey);
|
||||
const appVersion = useSettingsPageStatesStore(state => state.appVersion);
|
||||
@@ -91,7 +91,6 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
const isErrored = useDownloaderPageStatesStore((state) => state.isErrored);
|
||||
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
||||
const erroredDownloadId = useDownloaderPageStatesStore((state) => state.erroredDownloadId);
|
||||
const downloadConfiguration = useDownloaderPageStatesStore((state) => state.downloadConfiguration);
|
||||
const setIsErrored = useDownloaderPageStatesStore((state) => state.setIsErrored);
|
||||
const setIsErrorExpected = useDownloaderPageStatesStore((state) => state.setIsErrorExpected);
|
||||
const setErroredDownloadId = useDownloaderPageStatesStore((state) => state.setErroredDownloadId);
|
||||
@@ -122,7 +121,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
const hasRunYtDlpAutoUpdateRef = useRef(false);
|
||||
const isRegisteredToMacOsRef = useRef(false);
|
||||
|
||||
const fetchVideoMetadata = async (url: string, formatId?: string, playlistIndex?: string, selectedSubtitles?: string | null, resumeState?: DownloadState): Promise<RawVideoInfo | null> => {
|
||||
const fetchVideoMetadata = async (url: string, formatId?: string, playlistIndex?: string, selectedSubtitles?: string | null, resumeState?: DownloadState, downloadConfig?: DownloadConfiguration): Promise<RawVideoInfo | null> => {
|
||||
try {
|
||||
const args = [url, '--dump-single-json', '--no-warnings'];
|
||||
if (formatId) args.push('-f', formatId);
|
||||
@@ -132,6 +131,17 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
if (STRICT_DOWNLOADABILITY_CHECK && !formatId) args.push('--check-all-formats');
|
||||
if (STRICT_DOWNLOADABILITY_CHECK && formatId) args.push('--check-formats');
|
||||
|
||||
if ((USE_CUSTOM_COMMANDS && CUSTOM_COMMANDS && downloadConfig?.custom_command) || resumeState?.custom_command) {
|
||||
let customCommandArgs = null;
|
||||
if (resumeState?.custom_command) {
|
||||
customCommandArgs = resumeState.custom_command;
|
||||
} else if (CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfig?.custom_command)) {
|
||||
let customCommand = CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfig?.custom_command);
|
||||
customCommandArgs = customCommand ? customCommand.args : '';
|
||||
}
|
||||
if (customCommandArgs && customCommandArgs.trim() !== '') args.push(...customCommandArgs.split(' '));
|
||||
}
|
||||
|
||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && USE_PROXY && PROXY_URL) args.push('--proxy', PROXY_URL);
|
||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && USE_FORCE_INTERNET_PROTOCOL && FORCE_INTERNET_PROTOCOL) {
|
||||
if (FORCE_INTERNET_PROTOCOL === 'ipv4') {
|
||||
@@ -146,6 +156,19 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
} else if (IMPORT_COOKIES_FROM === 'file' && COOKIES_FILE) {
|
||||
args.push('--cookies', COOKIES_FILE);
|
||||
}
|
||||
}
|
||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (USE_SPONSORBLOCK || (resumeState?.sponsorblock_remove || resumeState?.sponsorblock_mark))) {
|
||||
if (SPONSORBLOCK_MODE === 'remove' || resumeState?.sponsorblock_remove) {
|
||||
let 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) {
|
||||
let sponsorblockMark = resumeState?.sponsorblock_mark || (SPONSORBLOCK_MARK === 'custom' ? (
|
||||
SPONSORBLOCK_MARK_CATEGORIES.length > 0 ? SPONSORBLOCK_MARK_CATEGORIES.join(',') : 'default'
|
||||
) : (SPONSORBLOCK_MARK));
|
||||
args.push('--sponsorblock-mark', sponsorblockMark);
|
||||
}
|
||||
};
|
||||
const command = Command.sidecar('binaries/yt-dlp', args);
|
||||
|
||||
@@ -246,20 +269,27 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
|
||||
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);
|
||||
const tempDownloadPathForYtdlp = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.%(ext)s`);
|
||||
const tempDownloadPath = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.${videoMetadata.ext}`);
|
||||
let downloadFilePath = resumeState?.filepath || await join(downloadDirPath, sanitizeFilename(`${videoMetadata.title}_${videoMetadata.resolution || 'unknown'}[${videoMetadata.id}].${videoMetadata.ext}`));
|
||||
const downloadId = resumeState?.download_id || ulid() /*generateDownloadId(videoMetadata.id, videoMetadata.webpage_url_domain)*/;
|
||||
// const tempDownloadPathForYtdlp = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.%(ext)s`);
|
||||
// const tempDownloadPath = await join(tempDownloadDirPath, `${downloadId}_${selectedFormat}.${videoMetadata.ext}`);
|
||||
// let downloadFilePath = resumeState?.filepath || await join(downloadDirPath, sanitizeFilename(`${videoMetadata.title}_${videoMetadata.resolution || 'unknown'}[${videoMetadata.id}].${videoMetadata.ext}`));
|
||||
let downloadFilePath: string | null = null;
|
||||
let processPid: number | null = null;
|
||||
const args = [
|
||||
url,
|
||||
'--newline',
|
||||
'--progress-template',
|
||||
'status:%(progress.status)s,progress:%(progress._percent_str)s,speed:%(progress.speed)f,downloaded:%(progress.downloaded_bytes)d,total:%(progress.total_bytes)d,eta:%(progress.eta)d',
|
||||
'--paths',
|
||||
`temp:${tempDownloadDirPath}`,
|
||||
'--paths',
|
||||
`home:${downloadDirPath}`,
|
||||
'--output',
|
||||
tempDownloadPathForYtdlp,
|
||||
// '--ffmpeg-location',
|
||||
// ffmpegPath,
|
||||
`%(title)s_%(resolution|unknown)s[${downloadId}].%(ext)s`,
|
||||
'--windows-filenames',
|
||||
'--restrict-filenames',
|
||||
'--exec',
|
||||
'after_move:echo Finalpath: {}',
|
||||
'-f',
|
||||
selectedFormat,
|
||||
'--no-mtime',
|
||||
@@ -277,11 +307,11 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
let customCommandArgs = null;
|
||||
if ((USE_CUSTOM_COMMANDS && CUSTOM_COMMANDS && downloadConfiguration.custom_command) || resumeState?.custom_command) {
|
||||
if ((USE_CUSTOM_COMMANDS && CUSTOM_COMMANDS && downloadConfig.custom_command) || resumeState?.custom_command) {
|
||||
if (resumeState?.custom_command) {
|
||||
customCommandArgs = resumeState.custom_command;
|
||||
} else if (CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfiguration.custom_command)) {
|
||||
let customCommand = CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfiguration.custom_command);
|
||||
} else if (CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfig.custom_command)) {
|
||||
let customCommand = CUSTOM_COMMANDS.find(cmd => cmd.id === downloadConfig.custom_command);
|
||||
customCommandArgs = customCommand ? customCommand.args : '';
|
||||
}
|
||||
|
||||
@@ -326,10 +356,10 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
let embedMetadata = 0;
|
||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfiguration.embed_metadata || resumeState?.embed_metadata || EMBED_VIDEO_METADATA || EMBED_AUDIO_METADATA)) {
|
||||
const shouldEmbedForVideo = (fileType === 'video+audio' || fileType === 'video') && (downloadConfiguration.embed_metadata || resumeState?.embed_metadata || (EMBED_VIDEO_METADATA && downloadConfiguration.embed_metadata === null));
|
||||
const shouldEmbedForAudio = fileType === 'audio' && (downloadConfiguration.embed_metadata || resumeState?.embed_metadata || (EMBED_AUDIO_METADATA && downloadConfiguration.embed_metadata === null));
|
||||
const shouldEmbedForUnknown = fileType === 'unknown' && (downloadConfiguration.embed_metadata || resumeState?.embed_metadata);
|
||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfig.embed_metadata || resumeState?.embed_metadata || EMBED_VIDEO_METADATA || EMBED_AUDIO_METADATA)) {
|
||||
const shouldEmbedForVideo = (fileType === 'video+audio' || fileType === 'video') && (downloadConfig.embed_metadata || resumeState?.embed_metadata || (EMBED_VIDEO_METADATA && downloadConfig.embed_metadata === null));
|
||||
const shouldEmbedForAudio = fileType === 'audio' && (downloadConfig.embed_metadata || resumeState?.embed_metadata || (EMBED_AUDIO_METADATA && downloadConfig.embed_metadata === null));
|
||||
const shouldEmbedForUnknown = fileType === 'unknown' && (downloadConfig.embed_metadata || resumeState?.embed_metadata);
|
||||
|
||||
if (shouldEmbedForUnknown || shouldEmbedForVideo || shouldEmbedForAudio) {
|
||||
embedMetadata = 1;
|
||||
@@ -338,7 +368,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
}
|
||||
|
||||
let embedThumbnail = 0;
|
||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfiguration.embed_thumbnail || resumeState?.embed_thumbnail || (fileType === 'audio' && EMBED_AUDIO_THUMBNAIL && downloadConfiguration.embed_thumbnail === null))) {
|
||||
if ((!USE_CUSTOM_COMMANDS && !resumeState?.custom_command) && (downloadConfig.embed_thumbnail || resumeState?.embed_thumbnail || (fileType === 'audio' && EMBED_AUDIO_THUMBNAIL && downloadConfig.embed_thumbnail === null))) {
|
||||
embedThumbnail = 1;
|
||||
args.push('--embed-thumbnail');
|
||||
}
|
||||
@@ -412,32 +442,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
setErroredDownloadId(downloadId);
|
||||
}
|
||||
} else {
|
||||
if (await fs.exists(tempDownloadPath)) {
|
||||
downloadFilePath = await generateSafeFilePath(downloadFilePath);
|
||||
LOG.info('NEODLP', `yt-dlp download completed with id: ${downloadId}, moving downloaded file from: "${tempDownloadPath}" to final destination: "${downloadFilePath}"`);
|
||||
await fs.copyFile(tempDownloadPath, downloadFilePath);
|
||||
await fs.remove(tempDownloadPath);
|
||||
}
|
||||
|
||||
downloadFilePathUpdater.mutate({ download_id: downloadId, filepath: downloadFilePath }, {
|
||||
onSuccess: (data) => {
|
||||
console.log("Download filepath updated successfully:", data);
|
||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to update download filepath:", error);
|
||||
}
|
||||
})
|
||||
|
||||
downloadStatusUpdater.mutate({ download_id: downloadId, download_status: 'completed' }, {
|
||||
onSuccess: (data) => {
|
||||
console.log("Download status updated successfully:", data);
|
||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to update download status:", error);
|
||||
}
|
||||
})
|
||||
LOG.info(`YT-DLP Download ${downloadId}`, `yt-dlp exited with code ${data.code}`);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -499,6 +504,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
sponsorblock_mark: sponsorblockMark,
|
||||
use_aria2: useAria2,
|
||||
custom_command: customCommandArgs,
|
||||
queue_config: null
|
||||
};
|
||||
downloadStateSaver.mutate(state, {
|
||||
onSuccess: (data) => {
|
||||
@@ -512,6 +518,36 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
} else {
|
||||
console.log(line);
|
||||
if (line.trim() !== '') LOG.info(`YT-DLP Download ${downloadId}`, line);
|
||||
|
||||
if (line.startsWith('Finalpath: ')) {
|
||||
downloadFilePath = line.replace('Finalpath: ', '').trim().replace(/^"|"$/g, '');
|
||||
const downloadedFileExt = downloadFilePath.split('.').pop();
|
||||
|
||||
// Update completion status after a short delay to ensure database states are propagated correctly
|
||||
console.log(`Download completed with ID: ${downloadId}, updating filepath and status after 1s delay...`);
|
||||
setTimeout(() => {
|
||||
LOG.info('NEODLP', `yt-dlp download completed with id: ${downloadId}`);
|
||||
downloadFilePathUpdater.mutate({ download_id: downloadId, filepath: downloadFilePath as string, ext: downloadedFileExt as string }, {
|
||||
onSuccess: (data) => {
|
||||
console.log("Download filepath updated successfully:", data);
|
||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to update download filepath:", error);
|
||||
}
|
||||
});
|
||||
|
||||
downloadStatusUpdater.mutate({ download_id: downloadId, download_status: 'completed' }, {
|
||||
onSuccess: (data) => {
|
||||
console.log("Download status updated successfully:", data);
|
||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to update download status:", error);
|
||||
}
|
||||
});
|
||||
}, 1000);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -591,7 +627,8 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
sponsorblock_remove: resumeState?.sponsorblock_remove || null,
|
||||
sponsorblock_mark: resumeState?.sponsorblock_mark || null,
|
||||
use_aria2: resumeState?.use_aria2 || 0,
|
||||
custom_command: resumeState?.custom_command || null
|
||||
custom_command: resumeState?.custom_command || null,
|
||||
queue_config: resumeState?.queue_config || ((!ongoingDownloads || ongoingDownloads && ongoingDownloads?.length < MAX_PARALLEL_DOWNLOADS) ? null : JSON.stringify(downloadConfig))
|
||||
}
|
||||
downloadStateSaver.mutate(state, {
|
||||
onSuccess: (data) => {
|
||||
@@ -683,7 +720,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
await startDownload(
|
||||
downloadState.playlist_id && downloadState.playlist_index ? downloadState.playlist_url : downloadState.url,
|
||||
downloadState.format_id,
|
||||
{
|
||||
downloadState.queue_config ? JSON.parse(downloadState.queue_config) : {
|
||||
output_format: null,
|
||||
embed_metadata: null,
|
||||
embed_thumbnail: null,
|
||||
@@ -785,7 +822,12 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
await startDownload(
|
||||
downloadToStart.url,
|
||||
downloadToStart.format_id,
|
||||
downloadConfiguration,
|
||||
downloadToStart.queue_config ? JSON.parse(downloadToStart.queue_config) : {
|
||||
output_format: null,
|
||||
embed_metadata: null,
|
||||
embed_thumbnail: null,
|
||||
custom_command: null
|
||||
},
|
||||
downloadToStart.subtitle_id,
|
||||
downloadToStart
|
||||
);
|
||||
|
||||
@@ -4,7 +4,7 @@ import { RawVideoInfo } from '@/types/video';
|
||||
import { createContext, useContext } from 'react';
|
||||
|
||||
interface AppContextType {
|
||||
fetchVideoMetadata: (url: string, formatId?: string, playlistIndex?: string, selectedSubtitles?: string | null, resumeState?: DownloadState) => Promise<RawVideoInfo | null>;
|
||||
fetchVideoMetadata: (url: string, formatId?: string, playlistIndex?: string, selectedSubtitles?: string | null, resumeState?: DownloadState, downloadConfig?: DownloadConfiguration) => Promise<RawVideoInfo | null>;
|
||||
startDownload: (url: string, selectedFormat: string, downloadConfig: DownloadConfiguration, selectedSubtitles?: string | null, resumeState?: DownloadState, playlistItems?: string) => Promise<void>;
|
||||
pauseDownload: (state: DownloadState) => Promise<void>;
|
||||
resumeDownload: (state: DownloadState) => Promise<void>;
|
||||
|
||||
@@ -203,7 +203,8 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
sponsorblock_remove = $29,
|
||||
sponsorblock_mark = $30,
|
||||
use_aria2 = $31,
|
||||
custom_command = $32
|
||||
custom_command = $32,
|
||||
queue_config = $33
|
||||
WHERE download_id = $1`,
|
||||
[
|
||||
downloadState.download_id,
|
||||
@@ -237,7 +238,8 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
downloadState.sponsorblock_remove,
|
||||
downloadState.sponsorblock_mark,
|
||||
downloadState.use_aria2,
|
||||
downloadState.custom_command
|
||||
downloadState.custom_command,
|
||||
downloadState.queue_config
|
||||
]
|
||||
)
|
||||
}
|
||||
@@ -273,8 +275,9 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
sponsorblock_remove,
|
||||
sponsorblock_mark,
|
||||
use_aria2,
|
||||
custom_command
|
||||
) 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, $31, $32)`,
|
||||
custom_command,
|
||||
queue_config
|
||||
) 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, $31, $32, $33)`,
|
||||
[
|
||||
downloadState.download_id,
|
||||
downloadState.download_status,
|
||||
@@ -307,7 +310,8 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
downloadState.sponsorblock_remove,
|
||||
downloadState.sponsorblock_mark,
|
||||
downloadState.use_aria2,
|
||||
downloadState.custom_command
|
||||
downloadState.custom_command,
|
||||
downloadState.queue_config
|
||||
]
|
||||
)
|
||||
}
|
||||
@@ -320,11 +324,11 @@ export const updateDownloadStatus = async (download_id: string, download_status:
|
||||
)
|
||||
}
|
||||
|
||||
export const updateDownloadFilePath = async (download_id: string, filepath: string) => {
|
||||
export const updateDownloadFilePath = async (download_id: string, filepath: string, ext: string) => {
|
||||
const db = await Database.load('sqlite:database.db')
|
||||
return await db.execute(
|
||||
'UPDATE downloads SET filepath = $2 WHERE download_id = $1',
|
||||
[download_id, filepath]
|
||||
'UPDATE downloads SET filepath = $2, ext = $3 WHERE download_id = $1',
|
||||
[download_id, filepath, ext]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -451,4 +455,4 @@ export const deleteKvPair = async (key: string) => {
|
||||
'DELETE FROM kv_store WHERE key = $1',
|
||||
[key]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,8 +31,8 @@ export function useUpdateDownloadStatus() {
|
||||
|
||||
export function useUpdateDownloadFilePath() {
|
||||
return useMutation({
|
||||
mutationFn: (data: { download_id: string; filepath: string }) =>
|
||||
updateDownloadFilePath(data.download_id, data.filepath)
|
||||
mutationFn: (data: { download_id: string; filepath: string, ext: string }) =>
|
||||
updateDownloadFilePath(data.download_id, data.filepath, data.ext)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -64,4 +64,4 @@ export function useDeleteKvPair() {
|
||||
return useMutation({
|
||||
mutationFn: (key: string) => deleteKvPair(key)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,7 @@ export interface DownloadState {
|
||||
sponsorblock_mark: string | null;
|
||||
use_aria2: number;
|
||||
custom_command: string | null;
|
||||
queue_config: string | null;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
@@ -81,6 +82,7 @@ export interface Download {
|
||||
sponsorblock_mark: string | null;
|
||||
use_aria2: number;
|
||||
custom_command: string | null;
|
||||
queue_config: string | null;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
@@ -92,4 +94,4 @@ export interface DownloadProgress {
|
||||
downloaded: number | null;
|
||||
total: number | null;
|
||||
eta: number | null;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user