mirror of
https://github.com/neosubhamoy/neodlp.git
synced 2025-12-19 01:32:57 +05:30
feat: added aria2 support and some other improvements
This commit is contained in:
21
README.md
21
README.md
@@ -41,14 +41,15 @@ After installing the extension you can do the following directly from the browse
|
||||
|
||||
- Windows (10 / 11)
|
||||
- Linux (Debian / Fedora / Arch Linux base)
|
||||
- MacOS (>10.3)
|
||||
- MacOS (>10.5)
|
||||
|
||||
> ⚠️ **NOTE:** Though most linux (debian/fedora/arch base) distros are supported but not all packages are tested on all these platforms, to save some time (and brain cells) and ship the software as fast as possible! (Currently only the debian package is tested on Ubuntu 24.04 LTS - So, other linux packages may have issues, test it yourself and feel free to report issues if you found one)
|
||||
|
||||
### 🤝 External Dependencies
|
||||
|
||||
- [YT-DLP](https://github.com/yt-dlp/yt-dlp) - The core CLI tool used to download Video/Audio from the Web (Hero of the show 😎)
|
||||
- [FFmpeg](https://www.ffmpeg.org) - Used for Video/Audio post-processing
|
||||
- [YT-DLP](https://github.com/yt-dlp/yt-dlp) (Latest Nightly) - The core CLI tool used to download video/audio from the web (Hero of the show 😎)
|
||||
- [FFmpeg & FFprobe](https://www.ffmpeg.org) (v7.1.1) - Used for video/audio post-processing
|
||||
- [Aria2](https://aria2.github.io) (v1.37.0) - Used as an external downloader for blazing fast downloads with yt-dlp
|
||||
|
||||
### ⬇️ Download and Installation
|
||||
|
||||
@@ -83,6 +84,7 @@ NeoDLP is and will be always FREE to Use and Open-Sourced for Everyone. On the o
|
||||
- [x] Add support for yt-dlp
|
||||
- [x] Add basic settings and customization
|
||||
- [x] Integrate with browsers
|
||||
- [x] Add aria2c support
|
||||
- [ ] Add more advanced settings and achive stability **(ongoing)**
|
||||
- [ ] Add media converter
|
||||
- [ ] Add multiple downloader engines
|
||||
@@ -125,8 +127,17 @@ npm run tauri build -- --config "./src-tauri/tauri.macos-x86_64.conf.json" #
|
||||
|
||||
### ⭕ Bug Report
|
||||
|
||||
Noticed any Bug? or Want to give me some suggetions? Always feel free to open a [GitHub Issue](https://github.com/neosubhamoy/neodlp/issues). I would love to hear from you...!!
|
||||
Noticed any Bug? or Want to give me some suggetion? Always feel free to open a [GitHub Issue](https://github.com/neosubhamoy/neodlp/issues). I would love to hear from you...!!
|
||||
|
||||
### 💫 Credits
|
||||
|
||||
- NeoDLP's 'Format Selection' options are inspired from the [Seal](https://github.com/JunkFood02/Seal) app by [@JunkFood02](https://github.com/JunkFood02)
|
||||
- Aria2 Linux x86_64 static build is provided by [@q3aql](https://github.com/q3aql/aria2-static-builds)
|
||||
- Aria2 MacOS x86_64 and ARM64 static builds are provided by [@tofuliang](https://github.com/tofuliang/aria2)
|
||||
|
||||
### 📝 License
|
||||
|
||||
NeoDLP is Licensed under the [MIT license](https://github.com/neosubhamoy/neodlp/blob/main/LICENSE). Anyone can view, modify, use (personal and commercial) or distribute it's sources without any attribution and extra permissions.
|
||||
NeoDLP is Licensed under the [MIT license](https://github.com/neosubhamoy/neodlp/blob/main/LICENSE). Anyone can view, modify, use (personal and commercial) or distribute it's sources without any attribution and extra permissions.
|
||||
|
||||
****
|
||||
An Open Sourced Project - Developed with ❤️ by **Subhamoy**
|
||||
3
src-tauri/binaries/aria2c-aarch64-apple-darwin
Normal file
3
src-tauri/binaries/aria2c-aarch64-apple-darwin
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:2c3e5c362d2c0e66552cd39c303337641affcfbb2a1f08b3c02dd7d50452b97f
|
||||
size 4915976
|
||||
3
src-tauri/binaries/aria2c-x86_64-apple-darwin
Normal file
3
src-tauri/binaries/aria2c-x86_64-apple-darwin
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:34d16e5eb78a6ea33256e2b87cdbdad37872f803479d2ddfff085792e1d907d3
|
||||
size 5268704
|
||||
3
src-tauri/binaries/aria2c-x86_64-pc-windows-msvc.exe
Normal file
3
src-tauri/binaries/aria2c-x86_64-pc-windows-msvc.exe
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:be2099c214f63a3cb4954b09a0becd6e2e34660b886d4c898d260febfe9d70c2
|
||||
size 5649408
|
||||
3
src-tauri/binaries/aria2c-x86_64-unknown-linux-gnu
Normal file
3
src-tauri/binaries/aria2c-x86_64-unknown-linux-gnu
Normal file
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:36f66dab69edcc44255d0dba90c93f5aa4a304ec60c7136d8c279dfc89c23e1d
|
||||
size 9666624
|
||||
@@ -25,6 +25,11 @@
|
||||
"args": true,
|
||||
"sidecar": true
|
||||
},
|
||||
{
|
||||
"name": "binaries/aria2c",
|
||||
"args": true,
|
||||
"sidecar": true
|
||||
},
|
||||
{
|
||||
"name": "pkexec",
|
||||
"cmd": "pkexec",
|
||||
@@ -49,6 +54,11 @@
|
||||
"name": "binaries/ffprobe",
|
||||
"args": true,
|
||||
"sidecar": true
|
||||
},
|
||||
{
|
||||
"name": "binaries/aria2c",
|
||||
"args": true,
|
||||
"sidecar": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -105,5 +105,80 @@ pub fn get_migrations() -> Vec<Migration> {
|
||||
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,
|
||||
}]
|
||||
}
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"externalBin": [
|
||||
"binaries/yt-dlp",
|
||||
"binaries/ffmpeg",
|
||||
"binaries/ffprobe"
|
||||
"binaries/ffprobe",
|
||||
"binaries/aria2c"
|
||||
],
|
||||
"linux": {
|
||||
"deb": {
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"externalBin": [
|
||||
"binaries/yt-dlp",
|
||||
"binaries/ffmpeg",
|
||||
"binaries/ffprobe"
|
||||
"binaries/ffprobe",
|
||||
"binaries/aria2c"
|
||||
],
|
||||
"resources": {
|
||||
"target/aarch64-apple-darwin/release/neodlp-msghost": "neodlp-msghost",
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"externalBin": [
|
||||
"binaries/yt-dlp",
|
||||
"binaries/ffmpeg",
|
||||
"binaries/ffprobe"
|
||||
"binaries/ffprobe",
|
||||
"binaries/aria2c"
|
||||
],
|
||||
"resources": {
|
||||
"target/x86_64-apple-darwin/release/neodlp-msghost": "neodlp-msghost",
|
||||
|
||||
@@ -38,7 +38,8 @@
|
||||
"externalBin": [
|
||||
"binaries/yt-dlp",
|
||||
"binaries/ffmpeg",
|
||||
"binaries/ffprobe"
|
||||
"binaries/ffprobe",
|
||||
"binaries/aria2c"
|
||||
],
|
||||
"resources": {
|
||||
"target/release/neodlp-msghost.exe": "neodlp-msghost.exe",
|
||||
|
||||
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