mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-19 19:02:59 +05:30
feat: added aria2 support and some other improvements
This commit is contained in:
45
src/App.tsx
45
src/App.tsx
@@ -80,6 +80,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
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 USE_ARIA2 = useSettingsPageStatesStore(state => state.settings.use_aria2);
|
||||
|
||||
const isErrored = useDownloaderPageStatesStore((state) => state.isErrored);
|
||||
const isErrorExpected = useDownloaderPageStatesStore((state) => state.isErrorExpected);
|
||||
@@ -247,7 +248,7 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
|
||||
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)));
|
||||
outputFormat = resumeState?.output_format || (fileType === 'video+audio' && VIDEO_FORMAT !== 'auto' ? VIDEO_FORMAT : (fileType === 'video' && VIDEO_FORMAT !== 'auto' ? VIDEO_FORMAT : (fileType === 'audio' && AUDIO_FORMAT !== 'auto' ? AUDIO_FORMAT : null)));
|
||||
if ((VIDEO_FORMAT !== 'auto' && fileType === 'video+audio') || (resumeState?.output_format && fileType === 'video+audio')) {
|
||||
if (ALWAYS_REENCODE_VIDEO) {
|
||||
args.push('--recode-video', resumeState?.output_format || VIDEO_FORMAT);
|
||||
@@ -316,8 +317,18 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
args.push('--sponsorblock-mark', sponsorblockMark);
|
||||
}
|
||||
}
|
||||
|
||||
let useAria2 = 0;
|
||||
if (USE_ARIA2 || resumeState?.use_aria2) {
|
||||
useAria2 = 1;
|
||||
args.push(
|
||||
'--downloader', 'aria2c',
|
||||
'--downloader', 'dash,m3u8:native',
|
||||
'--downloader-args', 'aria2c:-c -j 16 -x 16 -s 16 -k 1M --check-certificate=false'
|
||||
);
|
||||
}
|
||||
|
||||
if (resumeState) {
|
||||
if (resumeState || USE_ARIA2) {
|
||||
args.push('--continue');
|
||||
} else {
|
||||
args.push('--no-continue');
|
||||
@@ -369,7 +380,8 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
});
|
||||
|
||||
command.stdout.on('data', line => {
|
||||
if (line.startsWith('status:')) {
|
||||
if (line.startsWith('status:') || line.startsWith('[#')) {
|
||||
console.log(line);
|
||||
const currentProgress = parseProgressLine(line);
|
||||
const state: DownloadState = {
|
||||
download_id: downloadId,
|
||||
@@ -414,7 +426,8 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
embed_metadata: embedMetadata,
|
||||
embed_thumbnail: embedThumbnail,
|
||||
sponsorblock_remove: sponsorblockRemove,
|
||||
sponsorblock_mark: sponsorblockMark
|
||||
sponsorblock_mark: sponsorblockMark,
|
||||
use_aria2: useAria2
|
||||
};
|
||||
downloadStateSaver.mutate(state, {
|
||||
onSuccess: (data) => {
|
||||
@@ -504,7 +517,8 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
embed_metadata: resumeState?.embed_metadata || 0,
|
||||
embed_thumbnail: resumeState?.embed_thumbnail || 0,
|
||||
sponsorblock_remove: resumeState?.sponsorblock_remove || null,
|
||||
sponsorblock_mark: resumeState?.sponsorblock_mark || null
|
||||
sponsorblock_mark: resumeState?.sponsorblock_mark || null,
|
||||
use_aria2: resumeState?.use_aria2 || 0
|
||||
}
|
||||
downloadStateSaver.mutate(state, {
|
||||
onSuccess: (data) => {
|
||||
@@ -546,9 +560,28 @@ export default function App({ children }: { children: React.ReactNode }) {
|
||||
onSuccess: (data) => {
|
||||
console.log("Download status updated successfully:", data);
|
||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||
|
||||
/* re-check if the download is properly paused (if not try again after a small delay)
|
||||
as the pause opertion happens within high throughput of operations and have a high chgance of failure.
|
||||
*/
|
||||
if (isSuccessFetchingDownloadStates && downloadStates.find(state => state.download_id === downloadState.download_id)?.download_status !== 'paused') {
|
||||
console.log("Download status not updated to paused yet, retrying...");
|
||||
setTimeout(() => {
|
||||
downloadStatusUpdater.mutate({ download_id: downloadState.download_id, download_status: 'paused' }, {
|
||||
onSuccess: (data) => {
|
||||
console.log("Download status updated successfully on retry:", data);
|
||||
queryClient.invalidateQueries({ queryKey: ['download-states'] });
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error("Failed to update download status:", error);
|
||||
}
|
||||
});
|
||||
}, 200);
|
||||
}
|
||||
|
||||
// Reset the processing flag to ensure queue can be processed
|
||||
isProcessingQueueRef.current = false;
|
||||
|
||||
|
||||
// Process the queue after a short delay to ensure state is updated
|
||||
setTimeout(() => {
|
||||
processQueuedDownloads();
|
||||
|
||||
@@ -106,6 +106,7 @@ export default function SettingsPage() {
|
||||
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 useAria2 = useSettingsPageStatesStore(state => state.settings.use_aria2);
|
||||
|
||||
const websocketPort = useSettingsPageStatesStore(state => state.settings.websocket_port);
|
||||
const isChangingWebSocketPort = useSettingsPageStatesStore(state => state.isChangingWebSocketPort);
|
||||
@@ -466,6 +467,15 @@ export default function SettingsPage() {
|
||||
/>
|
||||
<Label htmlFor="max-retries" className="text-xs text-muted-foreground">(Current: {maxRetries}) (Default: 5, Maximum: 100)</Label>
|
||||
</div>
|
||||
<div className="aria2">
|
||||
<h3 className="font-semibold">Aria2</h3>
|
||||
<p className="text-xs text-muted-foreground mb-3">Use aria2c as external downloader (recommended for large files and unstable connections, resuming is not supported)</p>
|
||||
<Switch
|
||||
id="aria2"
|
||||
checked={useAria2}
|
||||
onCheckedChange={(checked) => saveSettingsKey('use_aria2', checked)}
|
||||
/>
|
||||
</div>
|
||||
</TabsContent>
|
||||
<TabsContent key="appearance" value="appearance" className="flex flex-col gap-4 min-h-[310px]">
|
||||
<div className="app-theme">
|
||||
|
||||
@@ -201,7 +201,8 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
embed_metadata = $27,
|
||||
embed_thumbnail = $28,
|
||||
sponsorblock_remove = $29,
|
||||
sponsorblock_mark = $30
|
||||
sponsorblock_mark = $30,
|
||||
use_aria2 = $31
|
||||
WHERE download_id = $1`,
|
||||
[
|
||||
downloadState.download_id,
|
||||
@@ -233,7 +234,8 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
downloadState.embed_metadata,
|
||||
downloadState.embed_thumbnail,
|
||||
downloadState.sponsorblock_remove,
|
||||
downloadState.sponsorblock_mark
|
||||
downloadState.sponsorblock_mark,
|
||||
downloadState.use_aria2
|
||||
]
|
||||
)
|
||||
}
|
||||
@@ -267,8 +269,9 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
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)`,
|
||||
sponsorblock_mark,
|
||||
use_aria2
|
||||
) 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)`,
|
||||
[
|
||||
downloadState.download_id,
|
||||
downloadState.download_status,
|
||||
@@ -299,7 +302,8 @@ export const saveDownloadState = async (downloadState: DownloadState) => {
|
||||
downloadState.embed_metadata,
|
||||
downloadState.embed_thumbnail,
|
||||
downloadState.sponsorblock_remove,
|
||||
downloadState.sponsorblock_mark
|
||||
downloadState.sponsorblock_mark,
|
||||
downloadState.use_aria2
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
@@ -151,6 +151,7 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
|
||||
sponsorblock_mark: 'default',
|
||||
sponsorblock_remove_categories: [],
|
||||
sponsorblock_mark_categories: [],
|
||||
use_aria2: false,
|
||||
// extension settings
|
||||
websocket_port: 53511
|
||||
},
|
||||
@@ -206,6 +207,7 @@ export const useSettingsPageStatesStore = create<SettingsPageStatesStore>((set)
|
||||
sponsorblock_mark: 'default',
|
||||
sponsorblock_remove_categories: [],
|
||||
sponsorblock_mark_categories: [],
|
||||
use_aria2: false,
|
||||
// extension settings
|
||||
websocket_port: 53511
|
||||
},
|
||||
|
||||
@@ -42,6 +42,7 @@ export interface DownloadState {
|
||||
embed_thumbnail: number;
|
||||
sponsorblock_remove: string | null;
|
||||
sponsorblock_mark: string | null;
|
||||
use_aria2: number;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
@@ -77,6 +78,7 @@ export interface Download {
|
||||
embed_thumbnail: number;
|
||||
sponsorblock_remove: string | null;
|
||||
sponsorblock_mark: string | null;
|
||||
use_aria2: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
@@ -32,6 +32,7 @@ export interface Settings {
|
||||
sponsorblock_mark: string;
|
||||
sponsorblock_remove_categories: string[];
|
||||
sponsorblock_mark_categories: string[];
|
||||
use_aria2: boolean;
|
||||
// extension settings
|
||||
websocket_port: number;
|
||||
}
|
||||
136
src/utils.ts
136
src/utils.ts
@@ -21,32 +21,130 @@ export function getRouteName(location: string, routes: Array<RoutesObj> = AllRou
|
||||
return lastPart ? lastPart.toUpperCase() : 'Dashboard';
|
||||
}
|
||||
|
||||
const convertToBytes = (value: number, unit: string): number => {
|
||||
switch (unit) {
|
||||
case 'B':
|
||||
return value;
|
||||
case 'KiB':
|
||||
return value * 1024;
|
||||
case 'MiB':
|
||||
return value * 1024 * 1024;
|
||||
case 'GiB':
|
||||
return value * 1024 * 1024 * 1024;
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
};
|
||||
|
||||
export const parseProgressLine = (line: string): DownloadProgress => {
|
||||
const progress: Partial<DownloadProgress> = {
|
||||
status: 'downloading'
|
||||
};
|
||||
|
||||
// Check if line contains both aria2c and yt-dlp format (combined format)
|
||||
if (line.includes(']status:')) {
|
||||
// Extract the status part after the closing bracket
|
||||
const statusIndex = line.indexOf(']status:');
|
||||
if (statusIndex !== -1) {
|
||||
const statusPart = line.substring(statusIndex + 1); // +1 to skip the ']'
|
||||
// Parse the yt-dlp format part
|
||||
statusPart.split(',').forEach(pair => {
|
||||
const [key, value] = pair.split(':');
|
||||
if (key && value) {
|
||||
switch (key.trim()) {
|
||||
case 'status':
|
||||
progress.status = value.trim();
|
||||
break;
|
||||
case 'progress':
|
||||
progress.progress = parseFloat(value.replace('%', '').trim());
|
||||
break;
|
||||
case 'speed':
|
||||
progress.speed = parseFloat(value);
|
||||
break;
|
||||
case 'downloaded':
|
||||
progress.downloaded = parseInt(value, 10);
|
||||
break;
|
||||
case 'total':
|
||||
progress.total = parseInt(value, 10);
|
||||
break;
|
||||
case 'eta':
|
||||
if (value.trim() !== 'NA') {
|
||||
progress.eta = parseInt(value, 10);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return progress as DownloadProgress;
|
||||
}
|
||||
|
||||
// Check if line is aria2c format only
|
||||
if (line.startsWith('[#') && line.includes('MiB') && line.includes('%')) {
|
||||
// Parse aria2c format: [#99f72b 2.5MiB/3.4MiB(75%) CN:1 DL:503KiB ETA:1s]
|
||||
|
||||
// Extract progress percentage
|
||||
const progressMatch = line.match(/\((\d+(?:\.\d+)?)%\)/);
|
||||
if (progressMatch) {
|
||||
progress.progress = parseFloat(progressMatch[1]);
|
||||
}
|
||||
|
||||
// Extract downloaded/total sizes
|
||||
const sizeMatch = line.match(/(\d+(?:\.\d+)?)(MiB|KiB|GiB|B)\/(\d+(?:\.\d+)?)(MiB|KiB|GiB|B)/);
|
||||
if (sizeMatch) {
|
||||
const downloaded = parseFloat(sizeMatch[1]);
|
||||
const downloadedUnit = sizeMatch[2];
|
||||
const total = parseFloat(sizeMatch[3]);
|
||||
const totalUnit = sizeMatch[4];
|
||||
|
||||
// Convert to bytes
|
||||
progress.downloaded = convertToBytes(downloaded, downloadedUnit);
|
||||
progress.total = convertToBytes(total, totalUnit);
|
||||
}
|
||||
|
||||
// Extract download speed
|
||||
const speedMatch = line.match(/DL:(\d+(?:\.\d+)?)(KiB|MiB|GiB|B)/);
|
||||
if (speedMatch) {
|
||||
const speed = parseFloat(speedMatch[1]);
|
||||
const speedUnit = speedMatch[2];
|
||||
progress.speed = convertToBytes(speed, speedUnit);
|
||||
}
|
||||
|
||||
// Extract ETA
|
||||
const etaMatch = line.match(/ETA:(\d+)s/);
|
||||
if (etaMatch) {
|
||||
progress.eta = parseInt(etaMatch[1], 10);
|
||||
}
|
||||
|
||||
return progress as DownloadProgress;
|
||||
}
|
||||
|
||||
// Original yt-dlp format: status:downloading,progress: 75.1%,speed:1022692.427018,downloaded:30289474,total:40331784,eta:9
|
||||
line.split(',').forEach(pair => {
|
||||
const [key, value] = pair.split(':');
|
||||
switch (key) {
|
||||
case 'status':
|
||||
progress.status = value.trim();
|
||||
break;
|
||||
case 'progress':
|
||||
progress.progress = parseFloat(value.replace('%', '').trim());
|
||||
break;
|
||||
case 'speed':
|
||||
progress.speed = parseFloat(value);
|
||||
break;
|
||||
case 'downloaded':
|
||||
progress.downloaded = parseInt(value, 10);
|
||||
break;
|
||||
case 'total':
|
||||
progress.total = parseInt(value, 10);
|
||||
break;
|
||||
case 'eta':
|
||||
progress.eta = parseInt(value, 10);
|
||||
break;
|
||||
if (key && value) {
|
||||
switch (key.trim()) {
|
||||
case 'status':
|
||||
progress.status = value.trim();
|
||||
break;
|
||||
case 'progress':
|
||||
progress.progress = parseFloat(value.replace('%', '').trim());
|
||||
break;
|
||||
case 'speed':
|
||||
progress.speed = parseFloat(value);
|
||||
break;
|
||||
case 'downloaded':
|
||||
progress.downloaded = parseInt(value, 10);
|
||||
break;
|
||||
case 'total':
|
||||
progress.total = parseInt(value, 10);
|
||||
break;
|
||||
case 'eta':
|
||||
if (value.trim() !== 'NA') {
|
||||
progress.eta = parseInt(value, 10);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user