Feat: Add progress bar to Youtube download (#481)

* download progress on console

* Feat: add a progress bar to Youtube download

* change download-on-state to interrupted if download failed
This commit is contained in:
Harry
2024-04-02 16:01:01 -07:00
committed by GitHub
parent f536643215
commit 2166a4aaf2
3 changed files with 80 additions and 31 deletions

View File

@@ -1,12 +1,13 @@
import { app } from "electron";
import path from "path";
import { exec } from "child_process";
import { exec, spawn } from "child_process";
import fs from "fs-extra";
import os from "os";
import log from "@main/logger";
import snakeCase from "lodash/snakeCase";
import settings from "@main/settings";
import url from 'url';
import url from "url";
import mainWin from "@main/window";
const __filename = url.fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
@@ -88,14 +89,15 @@ class Youtubedr {
quality?: string | number;
filename?: string;
directory?: string;
webContents?: Electron.WebContents;
} = {}
): Promise<string> {
const {
quality,
filename = this.getYtVideoId(url) + ".mp4",
directory = app.getPath("downloads"),
webContents = mainWin.win.webContents,
} = options;
const command = [
this.binFile,
"download",
@@ -106,37 +108,58 @@ class Youtubedr {
].join(" ");
logger.info(`Running command: ${command}`);
let currentSpeed = "";
return new Promise((resolve, reject) => {
exec(
command,
{
timeout: ONE_MINUTE * 15,
},
(error, stdout, stderr) => {
if (error) {
logger.error("error", error);
}
const proc = spawn(this.binFile, [
"download",
url,
`--quality=${quality || "medium"}`,
`--filename=${filename}`,
`--directory=${directory}`,
]);
if (stderr) {
logger.error("stderr", stderr);
}
proc.stdout.on("data", (data) => {
const output = data.toString();
const match = output.match(/iB (\d+) % \[/);
if (stdout) {
logger.debug(stdout);
}
if (fs.existsSync(path.join(directory, filename))) {
const stat = fs.statSync(path.join(directory, filename));
if (stat.size === 0) {
reject(new Error("Youtubedr download failed: empty file"));
} else {
resolve(path.join(directory, filename));
}
} else {
reject(new Error("Youtubedr download failed: unknown error"));
if (match) {
let speed = output.match(/\ ] (.*)/);
if (speed) {
currentSpeed = speed[1];
}
webContents.send("download-on-state", {
name: filename,
state: "progressing",
received: parseInt(match[1]),
speed: currentSpeed,
});
}
);
});
proc.on("close", (code) => {
if (code !== 0) {
webContents.send("download-on-state", {
name: filename,
state: "interrupted",
});
return reject(
new Error(`Youtubedr download failed with code: ${code}`)
);
}
if (fs.existsSync(path.join(directory, filename))) {
const stat = fs.statSync(path.join(directory, filename));
if (stat.size === 0) {
reject(new Error("Youtubedr download failed: empty file"));
} else {
resolve(path.join(directory, filename));
}
} else {
reject(new Error("Youtubedr download failed: unknown error"));
}
});
});
}

View File

@@ -10,6 +10,7 @@ import {
DialogTitle,
DialogContent,
DialogFooter,
Progress,
} from "@renderer/components/ui";
import { useNavigate } from "react-router-dom";
import { LoaderIcon } from "lucide-react";
@@ -22,10 +23,14 @@ export const YoutubeVideosSegment = () => {
null
);
const [submitting, setSubmitting] = useState(false);
const [progress, setProgress] = useState(0);
const [downloadSpeed, setDownloadSpeed] = useState(null);
const addToLibrary = () => {
let url = `https://www.youtube.com/watch?v=${selectedVideo?.videoId}`;
setSubmitting(true);
setProgress(0);
EnjoyApp.videos
.create(url, {
name: selectedVideo?.title,
@@ -64,6 +69,20 @@ export const YoutubeVideosSegment = () => {
fetchYoutubeVideos();
}, []);
useEffect(() => {
EnjoyApp.download.onState((_, downloadState) => {
const { state, received, speed } = downloadState;
if (state === "progressing") {
setProgress(received);
setDownloadSpeed(speed);
}
});
return () => {
EnjoyApp.download.removeAllListeners();
};
}, [submitting]);
if (!videos?.length) return null;
return (
@@ -101,7 +120,6 @@ export const YoutubeVideosSegment = () => {
<DialogHeader>
<DialogTitle>{t("downloadVideo")}</DialogTitle>
</DialogHeader>
<div className="flex items-center mb-4 bg-muted rounded-lg">
<div className="aspect-square h-28 overflow-hidden rounded-l-lg">
<img
@@ -119,7 +137,6 @@ export const YoutubeVideosSegment = () => {
</div>
</div>
</div>
<DialogFooter>
<Button
variant="ghost"
@@ -144,6 +161,14 @@ export const YoutubeVideosSegment = () => {
{t("downloadVideo")}
</Button>
</DialogFooter>
{submitting && (
<div>
<Progress value={progress} className="mb-2" />
<div className="text-xs line-clamp-1 mb-2 text-right">
{downloadSpeed}
</div>
</div>
)}
</DialogContent>
</Dialog>
</>

View File

@@ -19,6 +19,7 @@ type DownloadStateType = {
state: "progressing" | "interrupted" | "completed" | "cancelled";
received: number;
total: number;
speed?: string;
};
type NotificationType = {