Skip to content

Commit

Permalink
Feat: Add progress bar to Youtube download (#481)
Browse files Browse the repository at this point in the history
* download progress on console

* Feat: add a progress bar to Youtube download

* change download-on-state to interrupted if download failed
  • Loading branch information
HarryYC committed Apr 2, 2024
1 parent f536643 commit 2166a4a
Show file tree
Hide file tree
Showing 3 changed files with 78 additions and 29 deletions.
77 changes: 50 additions & 27 deletions enjoy/src/main/youtubedr.ts
Original file line number Diff line number Diff line change
@@ -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);
Expand Down Expand Up @@ -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",
Expand All @@ -106,37 +108,58 @@ class Youtubedr {
].join(" ");

logger.info(`Running command: ${command}`);
return new Promise((resolve, reject) => {
exec(
command,
{
timeout: ONE_MINUTE * 15,
},
(error, stdout, stderr) => {
if (error) {
logger.error("error", error);
}

if (stderr) {
logger.error("stderr", stderr);
}
let currentSpeed = "";

if (stdout) {
logger.debug(stdout);
return new Promise((resolve, reject) => {
const proc = spawn(this.binFile, [
"download",
url,
`--quality=${quality || "medium"}`,
`--filename=${filename}`,
`--directory=${directory}`,
]);

proc.stdout.on("data", (data) => {
const output = data.toString();
const match = output.match(/iB (\d+) % \[/);

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));
}
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 {
reject(new Error("Youtubedr download failed: unknown error"));
resolve(path.join(directory, filename));
}
} else {
reject(new Error("Youtubedr download failed: unknown error"));
}
);
});
});
}

Expand Down
29 changes: 27 additions & 2 deletions enjoy/src/renderer/components/videos/youtube-videos-segment.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
DialogTitle,
DialogContent,
DialogFooter,
Progress,
} from "@renderer/components/ui";
import { useNavigate } from "react-router-dom";
import { LoaderIcon } from "lucide-react";
Expand All @@ -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,
Expand Down Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand All @@ -119,7 +137,6 @@ export const YoutubeVideosSegment = () => {
</div>
</div>
</div>

<DialogFooter>
<Button
variant="ghost"
Expand All @@ -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>
</>
Expand Down
1 change: 1 addition & 0 deletions enjoy/src/types/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type DownloadStateType = {
state: "progressing" | "interrupted" | "completed" | "cancelled";
received: number;
total: number;
speed?: string;
};

type NotificationType = {
Expand Down

0 comments on commit 2166a4a

Please sign in to comment.