Initial version
						commit
						80411aeb08
					
				@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					[Desktop Entry]
 | 
				
			||||||
 | 
					Icon=/home/andrew/Pictures/icons/icon.png
 | 
				
			||||||
@ -0,0 +1,4 @@
 | 
				
			|||||||
 | 
					node_modules
 | 
				
			||||||
 | 
					release
 | 
				
			||||||
 | 
					package-lock.json
 | 
				
			||||||
 | 
					test.js
 | 
				
			||||||
@ -0,0 +1,200 @@
 | 
				
			|||||||
 | 
					const fs = require("fs");
 | 
				
			||||||
 | 
					const ytdl = require("ytdl-core");
 | 
				
			||||||
 | 
					const express = require("express");
 | 
				
			||||||
 | 
					const app = express();
 | 
				
			||||||
 | 
					const bodyParser = require("body-parser");
 | 
				
			||||||
 | 
					const ffmpeg = require("ffmpeg-static");
 | 
				
			||||||
 | 
					const os = require("os");
 | 
				
			||||||
 | 
					const cp = require("child_process");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.use(bodyParser.urlencoded({ extended: true }));
 | 
				
			||||||
 | 
					app.use(express.static(__dirname + "/public"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// To do
 | 
				
			||||||
 | 
					// Preset
 | 
				
			||||||
 | 
					// Download location selection
 | 
				
			||||||
 | 
					// Choosing correct encoding
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.get("/", (req, res) => {
 | 
				
			||||||
 | 
						res.sendFile(__dirname + "/index.html");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					async function getVideoInfo(url) {
 | 
				
			||||||
 | 
						const info = await ytdl.getInfo(url);
 | 
				
			||||||
 | 
						return info;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.post("/", (req, res) => {
 | 
				
			||||||
 | 
						const url = req.body.url;
 | 
				
			||||||
 | 
						getVideoInfo(url)
 | 
				
			||||||
 | 
							.then((video) =>
 | 
				
			||||||
 | 
								res.json({
 | 
				
			||||||
 | 
									status: true,
 | 
				
			||||||
 | 
									title: video.videoDetails.title,
 | 
				
			||||||
 | 
									formats: video.formats,
 | 
				
			||||||
 | 
									url: url,
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							.catch((error) =>
 | 
				
			||||||
 | 
								res.json({ status: false, message: "Use correct URL" })
 | 
				
			||||||
 | 
							);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Downloading video
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.post("/download", (req, res) => {
 | 
				
			||||||
 | 
						const itag = parseInt(req.body.audioTag || req.body.videoTag);
 | 
				
			||||||
 | 
						const url = req.body.url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Function to find video/audio info
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						async function findInfo(url, itag) {
 | 
				
			||||||
 | 
							const data = await ytdl.getInfo(url);
 | 
				
			||||||
 | 
							const format = ytdl.chooseFormat(data.formats, { quality: itag });
 | 
				
			||||||
 | 
							const title = data.videoDetails.title;
 | 
				
			||||||
 | 
							let extension;
 | 
				
			||||||
 | 
							if (format.hasVideo) {
 | 
				
			||||||
 | 
								extension = format.mimeType.split("; ")[0].split("/")[1];
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								if (format.audioCodec === "mp4a.40.2") {
 | 
				
			||||||
 | 
									extension = "m4a";
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									extension = format.audioCodec;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							let quality;
 | 
				
			||||||
 | 
							if (format.hasVideo) {
 | 
				
			||||||
 | 
								quality = format.qualityLabel;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								quality = format.audioBitrate + "kbps";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							const filename = title + "_" + quality + "." + extension
 | 
				
			||||||
 | 
							const info = {
 | 
				
			||||||
 | 
								format: format,
 | 
				
			||||||
 | 
								title: title,
 | 
				
			||||||
 | 
								extension: extension,
 | 
				
			||||||
 | 
								quality:quality,
 | 
				
			||||||
 | 
								filename:filename
 | 
				
			||||||
 | 
							};
 | 
				
			||||||
 | 
							return info;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						findInfo(url, itag).then((info) => {
 | 
				
			||||||
 | 
							const format = info.format;
 | 
				
			||||||
 | 
							const filename = info.filename;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// If video
 | 
				
			||||||
 | 
							if (format.hasVideo) {
 | 
				
			||||||
 | 
								let video = ytdl(url, { quality: itag });
 | 
				
			||||||
 | 
								let audio = ytdl(url, {
 | 
				
			||||||
 | 
									filter: "audioonly",
 | 
				
			||||||
 | 
									highWaterMark: 1 << 25,
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const ffmpegProcess = cp.spawn(
 | 
				
			||||||
 | 
									ffmpeg,
 | 
				
			||||||
 | 
									[
 | 
				
			||||||
 | 
										"-i",
 | 
				
			||||||
 | 
										`pipe:3`,
 | 
				
			||||||
 | 
										"-i",
 | 
				
			||||||
 | 
										`pipe:4`,
 | 
				
			||||||
 | 
										"-map",
 | 
				
			||||||
 | 
										"0:v",
 | 
				
			||||||
 | 
										"-map",
 | 
				
			||||||
 | 
										"1:a",
 | 
				
			||||||
 | 
										"-c:v",
 | 
				
			||||||
 | 
										"copy",
 | 
				
			||||||
 | 
										"-c:a",
 | 
				
			||||||
 | 
										"libmp3lame",
 | 
				
			||||||
 | 
										"-crf",
 | 
				
			||||||
 | 
										"27",
 | 
				
			||||||
 | 
										"-preset",
 | 
				
			||||||
 | 
										"fast",
 | 
				
			||||||
 | 
										"-movflags",
 | 
				
			||||||
 | 
										"frag_keyframe+empty_moov",
 | 
				
			||||||
 | 
										'-f', "mp4",
 | 
				
			||||||
 | 
										"-loglevel",
 | 
				
			||||||
 | 
										"error",
 | 
				
			||||||
 | 
										"-",
 | 
				
			||||||
 | 
									],
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										stdio: ["pipe", "pipe", "pipe", "pipe", "pipe"],
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								video.pipe(ffmpegProcess.stdio[3]);
 | 
				
			||||||
 | 
								audio.pipe(ffmpegProcess.stdio[4]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								res.header("Content-Disposition", "attachment; filename='" + filename + "'");
 | 
				
			||||||
 | 
								ffmpegProcess.stdio[1].pipe(res);
 | 
				
			||||||
 | 
								
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							// If audio 
 | 
				
			||||||
 | 
							 else {
 | 
				
			||||||
 | 
								res.header("Content-Disposition", "attachment; filename=" + filename);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								ytdl(url, { quality: itag })
 | 
				
			||||||
 | 
									.on("progress", (_, downloaded, size) => {
 | 
				
			||||||
 | 
										const progress = (downloaded / size) * 100;
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									// .pipe(fs.createWriteStream(os.homedir() + "/Downloads/" + filename))
 | 
				
			||||||
 | 
									.pipe(res);
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Off for now
 | 
				
			||||||
 | 
					app.post("", (req, res) => {
 | 
				
			||||||
 | 
						const itag = parseInt(req.body.audioTag || req.body.videoTag);
 | 
				
			||||||
 | 
						const url = req.body.url;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						async function findFormat(url, itag) {
 | 
				
			||||||
 | 
							const info = await ytdl.getInfo(url);
 | 
				
			||||||
 | 
							const format = ytdl.chooseFormat(info.formats, { quality: itag });
 | 
				
			||||||
 | 
							const title = info.videoDetails.title;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// Quality for filename
 | 
				
			||||||
 | 
							let quality;
 | 
				
			||||||
 | 
							if (format.hasVideo) {
 | 
				
			||||||
 | 
								quality = format.qualityLabel;
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								quality = format.audioBitrate + "kbps";
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// File extension for filename
 | 
				
			||||||
 | 
							let extension;
 | 
				
			||||||
 | 
							if (format.hasVideo) {
 | 
				
			||||||
 | 
								extension = format.mimeType.split("; ")[0].split("/")[1];
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								if (format.audioCodec === "mp4a.40.2") {
 | 
				
			||||||
 | 
									extension = "m4a";
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									extension = format.audioCodec;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							const filename = title + "_" + quality + "." + extension;
 | 
				
			||||||
 | 
							return filename;
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						findFormat(url, itag)
 | 
				
			||||||
 | 
							.then((filename) => {
 | 
				
			||||||
 | 
								res.header("Content-Disposition", "attachment; filename=.m4a");
 | 
				
			||||||
 | 
								ytdl(url, { quality: itag })
 | 
				
			||||||
 | 
									.on("progress", (_, downloaded, size) => {
 | 
				
			||||||
 | 
										const progress = (downloaded / size) * 100;
 | 
				
			||||||
 | 
										// console.log("Progress: " + progress + "%" )
 | 
				
			||||||
 | 
									})
 | 
				
			||||||
 | 
									// .pipe(fs.createWriteStream(os.homedir() + "/Downloads/" + filename))
 | 
				
			||||||
 | 
									.pipe(res);
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.catch((error) => {
 | 
				
			||||||
 | 
								console.log(error);
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.listen(3000, () => {
 | 
				
			||||||
 | 
						console.log("Server: http://localhost:3000");
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -0,0 +1,75 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html lang="en">
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <meta http-equiv="X-UA-Compatible" content="IE=edge">
 | 
				
			||||||
 | 
					    <meta name="viewport" content="width=device-width, initial-scale=1.0">
 | 
				
			||||||
 | 
					    <title>Youtube Video Downloader</title>
 | 
				
			||||||
 | 
					    <link rel="stylesheet" href="index.css">
 | 
				
			||||||
 | 
					    <script src="index.js" defer></script>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					    <!-- Theme toggle -->
 | 
				
			||||||
 | 
					    <div id="themeToggle" onclick="toggle()">
 | 
				
			||||||
 | 
					        <div id="themeToggleInside"></div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <h1>YouTube Downloader</h1>
 | 
				
			||||||
 | 
					    <input type="text" name="url" placeholder="Paste Video URL or ID here" id="url" autofocus>
 | 
				
			||||||
 | 
					    <!-- Get info button -->
 | 
				
			||||||
 | 
					    <button id="getInfo" onclick="clickAnimation('getInfo')">Get info</button>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <p id="incorrectMsg"></p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div id="loadingWrapper">
 | 
				
			||||||
 | 
					        <span>Loading</span>
 | 
				
			||||||
 | 
					        <svg version="1.1" id="L4" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px"
 | 
				
			||||||
 | 
					            y="0px" viewBox="0 0 100 100" enable-background="new 0 0 0 0" xml:space="preserve">
 | 
				
			||||||
 | 
					            <circle fill="rgb(84, 171, 222)" stroke="none" cx="6" cy="50" r="6">
 | 
				
			||||||
 | 
					                <animate attributeName="opacity" dur="1s" values="0;1;0" repeatCount="indefinite" begin="0.1" />
 | 
				
			||||||
 | 
					            </circle>
 | 
				
			||||||
 | 
					            <circle fill="rgb(84, 171, 222)" stroke="none" cx="26" cy="50" r="6">
 | 
				
			||||||
 | 
					                <animate attributeName="opacity" dur="1s" values="0;1;0" repeatCount="indefinite" begin="0.2" />
 | 
				
			||||||
 | 
					            </circle>
 | 
				
			||||||
 | 
					            <circle fill="rgb(84, 171, 222)" stroke="none" cx="46" cy="50" r="6">
 | 
				
			||||||
 | 
					                <animate attributeName="opacity" dur="1s" values="0;1;0" repeatCount="indefinite" begin="0.3" />
 | 
				
			||||||
 | 
					            </circle>
 | 
				
			||||||
 | 
					        </svg>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    <div id="hidden">
 | 
				
			||||||
 | 
					        <div id="btnContainer">
 | 
				
			||||||
 | 
					            <button class="toggleBtn" id="videoToggle">Video</button>
 | 
				
			||||||
 | 
					            <button class="toggleBtn" id="audioToggle">Audio</button>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					        <p id="title">Title: </p>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div id="videoList">
 | 
				
			||||||
 | 
					            <form action="/download" method="post">
 | 
				
			||||||
 | 
					                <label>Select Format - </label>
 | 
				
			||||||
 | 
					                <select name="videoTag" id="videoFormatSelect">
 | 
				
			||||||
 | 
					                </select>
 | 
				
			||||||
 | 
					                <br>
 | 
				
			||||||
 | 
					                <input type="hidden" name="url" class="url">
 | 
				
			||||||
 | 
					                <button type="submit" class="submitBtn">Download</button>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div id="audioList">
 | 
				
			||||||
 | 
					            <form action="/download" method="post">
 | 
				
			||||||
 | 
					                <label>Select Format - </label>
 | 
				
			||||||
 | 
					                <select name="audioTag" id="audioFormatSelect">
 | 
				
			||||||
 | 
					                </select>
 | 
				
			||||||
 | 
					                <br>
 | 
				
			||||||
 | 
					                <input type="hidden" name="url" class="url">
 | 
				
			||||||
 | 
					                <button type="submit" class="submitBtn" id="submitBtn" onclick="clickAnimation('submitBtn')">Download</button>
 | 
				
			||||||
 | 
					            </form>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					const { app, BrowserWindow } = require('electron')
 | 
				
			||||||
 | 
					const path = require('path')
 | 
				
			||||||
 | 
					require("./app.js")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function createWindow () {
 | 
				
			||||||
 | 
					  const win = new BrowserWindow({
 | 
				
			||||||
 | 
					    width: 800,
 | 
				
			||||||
 | 
					    height: 600
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  win.loadURL("http://localhost:3000")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.whenReady().then(() => {
 | 
				
			||||||
 | 
					  createWindow()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  app.on('activate', () => {
 | 
				
			||||||
 | 
					    if (BrowserWindow.getAllWindows().length === 0) {
 | 
				
			||||||
 | 
					      createWindow()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					app.on('window-all-closed', () => {
 | 
				
			||||||
 | 
					  if (process.platform !== 'darwin') {
 | 
				
			||||||
 | 
					    app.quit()
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					})
 | 
				
			||||||
@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
						"dependencies": {
 | 
				
			||||||
 | 
							"body-parser": "^1.20.0",
 | 
				
			||||||
 | 
							"express": "^4.18.1",
 | 
				
			||||||
 | 
							"ffmpeg-static": "^5.0.2",
 | 
				
			||||||
 | 
							"ytdl-core": "^4.11.0"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"name": "ytdownloader",
 | 
				
			||||||
 | 
						"version": "1.0.0",
 | 
				
			||||||
 | 
						"main": "main.js",
 | 
				
			||||||
 | 
						"scripts": {
 | 
				
			||||||
 | 
							"start": "electron .",
 | 
				
			||||||
 | 
							"windows": "electron-builder -w",
 | 
				
			||||||
 | 
							"linux": "electron-builder -l",
 | 
				
			||||||
 | 
							"mac": "electron-builder -m"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"keywords": [],
 | 
				
			||||||
 | 
						"author": "Andrew",
 | 
				
			||||||
 | 
						"license": "MIT",
 | 
				
			||||||
 | 
						"description": "Download videos and audios from YouTube",
 | 
				
			||||||
 | 
						"devDependencies": {
 | 
				
			||||||
 | 
							"electron": "^19.0.9",
 | 
				
			||||||
 | 
							"electron-builder": "^23.1.0"
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
						"build": {
 | 
				
			||||||
 | 
							"productName": "YTDownloader",
 | 
				
			||||||
 | 
							"appId": "com.andrew.ytdownloader",
 | 
				
			||||||
 | 
							"mac": {
 | 
				
			||||||
 | 
								"category": "Utility",
 | 
				
			||||||
 | 
								"target": [
 | 
				
			||||||
 | 
									"dmg"
 | 
				
			||||||
 | 
								]
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"dmg": {
 | 
				
			||||||
 | 
								"contents": [
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										"x": 130,
 | 
				
			||||||
 | 
										"y": 220
 | 
				
			||||||
 | 
									},
 | 
				
			||||||
 | 
									{
 | 
				
			||||||
 | 
										"x": 410,
 | 
				
			||||||
 | 
										"y": 220,
 | 
				
			||||||
 | 
										"type": "link",
 | 
				
			||||||
 | 
										"path": "/Applications"
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"sign": false
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"asar": true,
 | 
				
			||||||
 | 
							"directories": {
 | 
				
			||||||
 | 
								"buildResources": "resources",
 | 
				
			||||||
 | 
								"output": "release"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"linux": {
 | 
				
			||||||
 | 
								"target": [
 | 
				
			||||||
 | 
									"AppImage"
 | 
				
			||||||
 | 
								],
 | 
				
			||||||
 | 
								"category": "Utility"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"win": {
 | 
				
			||||||
 | 
								"target": "nsis"
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							"nsis": {
 | 
				
			||||||
 | 
								"allowToChangeInstallationDirectory": true,
 | 
				
			||||||
 | 
								"oneClick": false
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 4.8 KiB  | 
@ -0,0 +1,149 @@
 | 
				
			|||||||
 | 
					body {
 | 
				
			||||||
 | 
						font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI",
 | 
				
			||||||
 | 
							Roboto, Oxygen, Ubuntu, Cantarell, "Open Sans", "Helvetica Neue",
 | 
				
			||||||
 | 
							sans-serif;
 | 
				
			||||||
 | 
						text-align: center;
 | 
				
			||||||
 | 
						font-size: xx-large;
 | 
				
			||||||
 | 
						padding: 10px;
 | 
				
			||||||
 | 
						background-color: whitesmoke;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#title {
 | 
				
			||||||
 | 
						font-size: x-large;
 | 
				
			||||||
 | 
						font-family: sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					h1 {
 | 
				
			||||||
 | 
						font-size: 50px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					input[type="text"] {
 | 
				
			||||||
 | 
						padding: 10px;
 | 
				
			||||||
 | 
						border-radius: 10px;
 | 
				
			||||||
 | 
						border: 1px solid gray;
 | 
				
			||||||
 | 
						outline: none;
 | 
				
			||||||
 | 
						font-size: large;
 | 
				
			||||||
 | 
						width: 70%;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#getInfo {
 | 
				
			||||||
 | 
						padding: 10px;
 | 
				
			||||||
 | 
						background-color: rgb(64, 227, 64);
 | 
				
			||||||
 | 
						border: none;
 | 
				
			||||||
 | 
						border-radius: 8px;
 | 
				
			||||||
 | 
						color: white;
 | 
				
			||||||
 | 
						font-size: large;
 | 
				
			||||||
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						/* animation-name: clickAnimation; */
 | 
				
			||||||
 | 
						animation-duration: .5s;
 | 
				
			||||||
 | 
						animation-timing-function: linear;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@keyframes clickAnimation {
 | 
				
			||||||
 | 
						0% {background-color: rgb(64, 227, 64);}
 | 
				
			||||||
 | 
						50% {background-color: rgb(40, 126, 40);}
 | 
				
			||||||
 | 
						100% {background-color: rgb(64, 227, 64);}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#hidden {
 | 
				
			||||||
 | 
						display: none;
 | 
				
			||||||
 | 
						/* display: inline-block; */
 | 
				
			||||||
 | 
						background-color: rgb(143, 239, 207);
 | 
				
			||||||
 | 
						border-radius: 10px;
 | 
				
			||||||
 | 
						width: 80%;
 | 
				
			||||||
 | 
						padding: 10px;
 | 
				
			||||||
 | 
						color:black;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#btnContainer {
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						flex-direction: row;
 | 
				
			||||||
 | 
						justify-content: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.toggleBtn {
 | 
				
			||||||
 | 
						width: 50%;
 | 
				
			||||||
 | 
						font-size: x-large;
 | 
				
			||||||
 | 
						border: none;
 | 
				
			||||||
 | 
						background-color: rgb(108, 231, 190);
 | 
				
			||||||
 | 
						border-radius: 10px;
 | 
				
			||||||
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
						padding: 8px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					select {
 | 
				
			||||||
 | 
						padding: 15px;
 | 
				
			||||||
 | 
						background-color: lightgreen;
 | 
				
			||||||
 | 
						border: none;
 | 
				
			||||||
 | 
						border-radius: 8px;
 | 
				
			||||||
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
						font-size: large;
 | 
				
			||||||
 | 
						margin: 8px;
 | 
				
			||||||
 | 
						outline:none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					label {
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
 | 
						top: 3px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#videoList,
 | 
				
			||||||
 | 
					#audioList {
 | 
				
			||||||
 | 
						display: none;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.submitBtn {
 | 
				
			||||||
 | 
						padding: 15px;
 | 
				
			||||||
 | 
						margin: 15px;
 | 
				
			||||||
 | 
						border-radius: 8px;
 | 
				
			||||||
 | 
						background-color: rgb(64, 227, 64);
 | 
				
			||||||
 | 
						color: white;
 | 
				
			||||||
 | 
						border: none;
 | 
				
			||||||
 | 
						font-size: large;
 | 
				
			||||||
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
						
 | 
				
			||||||
 | 
						animation-duration: .5s;
 | 
				
			||||||
 | 
						animation-timing-function: linear;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#incorrectMsg {
 | 
				
			||||||
 | 
						color: rgb(250, 59, 59);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#loadingWrapper{
 | 
				
			||||||
 | 
						display:none;
 | 
				
			||||||
 | 
						flex-direction: row;
 | 
				
			||||||
 | 
						justify-content: center;
 | 
				
			||||||
 | 
						align-items: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					svg {
 | 
				
			||||||
 | 
						width: 100px;
 | 
				
			||||||
 | 
						height: 100px;
 | 
				
			||||||
 | 
						display: inline-block;
 | 
				
			||||||
 | 
						margin-left:20px
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#themeToggle {
 | 
				
			||||||
 | 
						width: 55px;
 | 
				
			||||||
 | 
						height: 30px;
 | 
				
			||||||
 | 
						background-color: rgb(147, 174, 185);
 | 
				
			||||||
 | 
						border-radius: 40px;
 | 
				
			||||||
 | 
						display: flex;
 | 
				
			||||||
 | 
						cursor: pointer;
 | 
				
			||||||
 | 
						transition: linear;
 | 
				
			||||||
 | 
						transition-duration: 0.4s;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#themeToggleInside {
 | 
				
			||||||
 | 
						background-color: rgb(255, 255, 255);
 | 
				
			||||||
 | 
						border-radius: 30px;
 | 
				
			||||||
 | 
						width: 22px;
 | 
				
			||||||
 | 
						height: 22px;
 | 
				
			||||||
 | 
						margin: 4px;
 | 
				
			||||||
 | 
						position: relative;
 | 
				
			||||||
 | 
						transition: linear;
 | 
				
			||||||
 | 
						transition-duration: 0.4s;
 | 
				
			||||||
 | 
						left:0px;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -0,0 +1,170 @@
 | 
				
			|||||||
 | 
					const videoToggle = document.getElementById("videoToggle");
 | 
				
			||||||
 | 
					const audioToggle = document.getElementById("audioToggle");
 | 
				
			||||||
 | 
					const incorrectMsg = document.getElementById("incorrectMsg");
 | 
				
			||||||
 | 
					const loadingMsg = document.getElementById("loadingWrapper")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getInfo() {
 | 
				
			||||||
 | 
						incorrectMsg.textContent = "";
 | 
				
			||||||
 | 
						loadingMsg.style.display = "flex";
 | 
				
			||||||
 | 
						const url = document.getElementById("url").value;
 | 
				
			||||||
 | 
						const options = {
 | 
				
			||||||
 | 
							method: "POST",
 | 
				
			||||||
 | 
							body: "url=" + url,
 | 
				
			||||||
 | 
							headers: {
 | 
				
			||||||
 | 
								"Content-Type": "Application/x-www-form-urlencoded",
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						fetch("/", options)
 | 
				
			||||||
 | 
							.then((response) => response.json())
 | 
				
			||||||
 | 
							.then((data) => {
 | 
				
			||||||
 | 
								console.log(data.formats);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								const urlElements = document.querySelectorAll(".url");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								urlElements.forEach((element) => {
 | 
				
			||||||
 | 
									element.value = data.url;
 | 
				
			||||||
 | 
								});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								if (data.status == true) {
 | 
				
			||||||
 | 
									loadingMsg.style.display = "none";
 | 
				
			||||||
 | 
									document.getElementById("hidden").style.display =
 | 
				
			||||||
 | 
										"inline-block";
 | 
				
			||||||
 | 
									document.getElementById("title").innerHTML =
 | 
				
			||||||
 | 
										"<b>Title</b>: " + data.title;
 | 
				
			||||||
 | 
									document.getElementById("videoList").style.display = "block";
 | 
				
			||||||
 | 
									videoToggle.style.backgroundColor = "rgb(67, 212, 164)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
									for (let format of data.formats) {
 | 
				
			||||||
 | 
										let sizeSplitted = (Number(format.contentLength) / 1000000)
 | 
				
			||||||
 | 
											.toString().split(".")
 | 
				
			||||||
 | 
										let size = sizeSplitted[0] + sizeSplitted[1].slice(0,1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										size = size + " MB";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// For videos
 | 
				
			||||||
 | 
										if (format.hasVideo && format.contentLength && format.container == "mp4") {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
												const itag = format.itag;
 | 
				
			||||||
 | 
												const element =
 | 
				
			||||||
 | 
													"<option value='" +
 | 
				
			||||||
 | 
													itag +
 | 
				
			||||||
 | 
													"'>" +
 | 
				
			||||||
 | 
													format.qualityLabel +
 | 
				
			||||||
 | 
													" | " +
 | 
				
			||||||
 | 
													format.container +
 | 
				
			||||||
 | 
													" | " +
 | 
				
			||||||
 | 
													size +
 | 
				
			||||||
 | 
													"</option>";
 | 
				
			||||||
 | 
												document.getElementById(
 | 
				
			||||||
 | 
													"videoFormatSelect"
 | 
				
			||||||
 | 
												).innerHTML += element;
 | 
				
			||||||
 | 
											
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
										// For audios
 | 
				
			||||||
 | 
										else {
 | 
				
			||||||
 | 
											const pattern = /^mp*4a[0-9.]+$/g;
 | 
				
			||||||
 | 
											let audioCodec;
 | 
				
			||||||
 | 
											const itag = format.itag;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
											if (pattern.test(format.audioCodec)) {
 | 
				
			||||||
 | 
												audioCodec = "mp4a";
 | 
				
			||||||
 | 
											} else {
 | 
				
			||||||
 | 
												audioCodec = format.audioCodec;
 | 
				
			||||||
 | 
											}
 | 
				
			||||||
 | 
											const element =
 | 
				
			||||||
 | 
												"<option value='" +
 | 
				
			||||||
 | 
												itag +
 | 
				
			||||||
 | 
												"'>" +
 | 
				
			||||||
 | 
												format.audioBitrate +
 | 
				
			||||||
 | 
												" kbps" +
 | 
				
			||||||
 | 
												" | " +
 | 
				
			||||||
 | 
												audioCodec +
 | 
				
			||||||
 | 
												" | " +
 | 
				
			||||||
 | 
												size +
 | 
				
			||||||
 | 
												"</option>";
 | 
				
			||||||
 | 
											document.getElementById(
 | 
				
			||||||
 | 
												"audioFormatSelect"
 | 
				
			||||||
 | 
											).innerHTML += element;
 | 
				
			||||||
 | 
										}
 | 
				
			||||||
 | 
									}
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									loadingMsg.style.display = "none";
 | 
				
			||||||
 | 
									incorrectMsg.textContent = "Some error has occured";
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
							.catch((error) => {
 | 
				
			||||||
 | 
								if (error) {
 | 
				
			||||||
 | 
									loadingMsg.style.display = "none";
 | 
				
			||||||
 | 
									incorrectMsg.textContent = error;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.getElementById("getInfo").addEventListener("click", (event) => {
 | 
				
			||||||
 | 
						getInfo();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.getElementById("url").addEventListener("keypress", (event) => {
 | 
				
			||||||
 | 
						if (event.key == "Enter") {
 | 
				
			||||||
 | 
							getInfo();
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					videoToggle.addEventListener("click", (event) => {
 | 
				
			||||||
 | 
						videoToggle.style.backgroundColor = "rgb(67, 212, 164)";
 | 
				
			||||||
 | 
						audioToggle.style.backgroundColor = "rgb(108, 231, 190)";
 | 
				
			||||||
 | 
						document.getElementById("audioList").style.display = "none";
 | 
				
			||||||
 | 
						document.getElementById("videoList").style.display = "block";
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					audioToggle.addEventListener("click", (event) => {
 | 
				
			||||||
 | 
						audioToggle.style.backgroundColor = "rgb(67, 212, 164)";
 | 
				
			||||||
 | 
						videoToggle.style.backgroundColor = "rgb(108, 231, 190)";
 | 
				
			||||||
 | 
						document.getElementById("videoList").style.display = "none";
 | 
				
			||||||
 | 
						document.getElementById("audioList").style.display = "block";
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Toggle theme
 | 
				
			||||||
 | 
					let darkTheme = false;
 | 
				
			||||||
 | 
					let button = document.getElementById("themeToggle");
 | 
				
			||||||
 | 
					let circle = document.getElementById("themeToggleInside");
 | 
				
			||||||
 | 
					function toggle() {
 | 
				
			||||||
 | 
						if (darkTheme == false) {
 | 
				
			||||||
 | 
							circle.style.left = "25px";
 | 
				
			||||||
 | 
							button.style.backgroundColor = "rgb(80, 193, 238)";
 | 
				
			||||||
 | 
							darkTheme = true;
 | 
				
			||||||
 | 
							document.body.style.backgroundColor = "rgb(50,50,50)";
 | 
				
			||||||
 | 
							document.getElementById("hidden").style.backgroundColor = "rgb(143, 239, 207)"
 | 
				
			||||||
 | 
							document.body.style.color = "whitesmoke";
 | 
				
			||||||
 | 
							localStorage.setItem("theme", "dark");
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							circle.style.left = "0px";
 | 
				
			||||||
 | 
							darkTheme = false;
 | 
				
			||||||
 | 
							button.style.backgroundColor = "rgb(147, 174, 185)";
 | 
				
			||||||
 | 
							document.body.style.backgroundColor = "whitesmoke";
 | 
				
			||||||
 | 
							document.getElementById("hidden").style.backgroundColor = "rgb(203, 253, 236)"
 | 
				
			||||||
 | 
							document.body.style.color = "black";
 | 
				
			||||||
 | 
							localStorage.setItem("theme", "light");
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const storageTheme = localStorage.getItem("theme");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					if (storageTheme == "dark") {
 | 
				
			||||||
 | 
						darkTheme = false;
 | 
				
			||||||
 | 
						toggle();
 | 
				
			||||||
 | 
					} else if (storageTheme == "light") {
 | 
				
			||||||
 | 
						darkTheme = true;
 | 
				
			||||||
 | 
						toggle();
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					////
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function clickAnimation(id) {
 | 
				
			||||||
 | 
						document.getElementById(id).style.animationName = "clickAnimation";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						setTimeout(() => {
 | 
				
			||||||
 | 
							document.getElementById("getInfo").style.animationName = "";
 | 
				
			||||||
 | 
						}, 500);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
											
												Binary file not shown.
											
										
									
								
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 279 KiB  | 
											
												Binary file not shown.
											
										
									
								| 
		 After Width: | Height: | Size: 4.8 KiB  | 
					Loading…
					
					
				
		Reference in New Issue