Compare commits
14 Commits
add-licens
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5112ff5ae7 | ||
|
|
876fb9686b | ||
|
|
fff8845c9d | ||
|
|
55df78ff94 | ||
|
|
88fa7a0d93 | ||
|
|
80e36b02c3 | ||
|
|
67dd6f3f39 | ||
|
|
e8c4ddcaaa | ||
|
|
f2f3fee52b | ||
|
|
e71de57d86 | ||
|
|
db4f403e16 | ||
|
|
fc22ca529d | ||
|
|
32da4e4466 | ||
|
|
36491a761a |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,4 +1,3 @@
|
|||||||
node_modules
|
|
||||||
package-lock.json
|
package-lock.json
|
||||||
*.mp4
|
node_modules/
|
||||||
*.mp3
|
logs.log
|
||||||
|
|||||||
2
LICENSE
2
LICENSE
@@ -1,6 +1,6 @@
|
|||||||
MIT License
|
MIT License
|
||||||
|
|
||||||
Copyright (c) 2019 Benjamin Kyd
|
Copyright (c) 2020 Benjamin Kyd
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
|||||||
BIN
Screenshot_27.png
Normal file
BIN
Screenshot_27.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
65
legacy/.gitignore
vendored
65
legacy/.gitignore
vendored
@@ -1,61 +1,4 @@
|
|||||||
# Logs
|
node_modules
|
||||||
logs
|
package-lock.json
|
||||||
*.log
|
*.mp4
|
||||||
npm-debug.log*
|
*.mp3
|
||||||
yarn-debug.log*
|
|
||||||
yarn-error.log*
|
|
||||||
|
|
||||||
# Runtime data
|
|
||||||
pids
|
|
||||||
*.pid
|
|
||||||
*.seed
|
|
||||||
*.pid.lock
|
|
||||||
|
|
||||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
|
||||||
lib-cov
|
|
||||||
|
|
||||||
# Coverage directory used by tools like istanbul
|
|
||||||
coverage
|
|
||||||
|
|
||||||
# nyc test coverage
|
|
||||||
.nyc_output
|
|
||||||
|
|
||||||
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
|
||||||
.grunt
|
|
||||||
|
|
||||||
# Bower dependency directory (https://bower.io/)
|
|
||||||
bower_components
|
|
||||||
|
|
||||||
# node-waf configuration
|
|
||||||
.lock-wscript
|
|
||||||
|
|
||||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
|
||||||
build/Release
|
|
||||||
|
|
||||||
# Dependency directories
|
|
||||||
node_modules/
|
|
||||||
jspm_packages/
|
|
||||||
|
|
||||||
# TypeScript v1 declaration files
|
|
||||||
typings/
|
|
||||||
|
|
||||||
# Optional npm cache directory
|
|
||||||
.npm
|
|
||||||
|
|
||||||
# Optional eslint cache
|
|
||||||
.eslintcache
|
|
||||||
|
|
||||||
# Optional REPL history
|
|
||||||
.node_repl_history
|
|
||||||
|
|
||||||
# Output of 'npm pack'
|
|
||||||
*.tgz
|
|
||||||
|
|
||||||
# Yarn Integrity file
|
|
||||||
.yarn-integrity
|
|
||||||
|
|
||||||
# dotenv environment variables file
|
|
||||||
.env
|
|
||||||
|
|
||||||
# next.js build output
|
|
||||||
.next
|
|
||||||
|
|||||||
@@ -1,40 +1 @@
|
|||||||
const fs = require('fs');
|
require('./src/index').main();
|
||||||
const ytdl = require('ytdl-core');
|
|
||||||
|
|
||||||
let videos = [];
|
|
||||||
|
|
||||||
if (!fs.existsSync('./videos.txt')) {
|
|
||||||
console.log('Could not find videos.txt, make sure it exists and has youtube links, each on new lines');
|
|
||||||
process.exit();
|
|
||||||
}
|
|
||||||
|
|
||||||
let text = fs.readFileSync('./videos.txt').toString();
|
|
||||||
text.split('\n').forEach((line) => {
|
|
||||||
videos.push(line.replace(/\r/, ''));
|
|
||||||
});
|
|
||||||
|
|
||||||
videos.forEach((url) => {
|
|
||||||
if (ytdl.validateURL(url)) {
|
|
||||||
ytdl.getInfo(url, (err, info) => {
|
|
||||||
if (err) {
|
|
||||||
console.log(`An error occured while downloading '${url}'`)
|
|
||||||
} else {
|
|
||||||
let title = info.title;
|
|
||||||
|
|
||||||
try {
|
|
||||||
let stream = ytdl(url, {quality: 'highest'}).pipe(fs.createWriteStream(`${title}.mp4`));
|
|
||||||
stream.on('finish', () => console.log(`Finish downloading ${title}`));
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`An error occured while downloaing '${title}' retrying`)
|
|
||||||
try {
|
|
||||||
let stream = ytdl(url, {quality: 'highest'}).pipe(fs.createWriteStream(`${title}.mp4`));
|
|
||||||
stream.on('finish', () => console.log(`Finished downloading '${title}'`));
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Unable to download '${title}'`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
console.log(`Downloading '${title}'`);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
} else console.log(`Video ${url} was not found`);
|
|
||||||
});
|
|
||||||
|
|||||||
61
legacy/legacy/.gitignore
vendored
Normal file
61
legacy/legacy/.gitignore
vendored
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
# Logs
|
||||||
|
logs
|
||||||
|
*.log
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
|
||||||
|
# Runtime data
|
||||||
|
pids
|
||||||
|
*.pid
|
||||||
|
*.seed
|
||||||
|
*.pid.lock
|
||||||
|
|
||||||
|
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||||
|
lib-cov
|
||||||
|
|
||||||
|
# Coverage directory used by tools like istanbul
|
||||||
|
coverage
|
||||||
|
|
||||||
|
# nyc test coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
|
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
|
||||||
|
.grunt
|
||||||
|
|
||||||
|
# Bower dependency directory (https://bower.io/)
|
||||||
|
bower_components
|
||||||
|
|
||||||
|
# node-waf configuration
|
||||||
|
.lock-wscript
|
||||||
|
|
||||||
|
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||||
|
build/Release
|
||||||
|
|
||||||
|
# Dependency directories
|
||||||
|
node_modules/
|
||||||
|
jspm_packages/
|
||||||
|
|
||||||
|
# TypeScript v1 declaration files
|
||||||
|
typings/
|
||||||
|
|
||||||
|
# Optional npm cache directory
|
||||||
|
.npm
|
||||||
|
|
||||||
|
# Optional eslint cache
|
||||||
|
.eslintcache
|
||||||
|
|
||||||
|
# Optional REPL history
|
||||||
|
.node_repl_history
|
||||||
|
|
||||||
|
# Output of 'npm pack'
|
||||||
|
*.tgz
|
||||||
|
|
||||||
|
# Yarn Integrity file
|
||||||
|
.yarn-integrity
|
||||||
|
|
||||||
|
# dotenv environment variables file
|
||||||
|
.env
|
||||||
|
|
||||||
|
# next.js build output
|
||||||
|
.next
|
||||||
40
legacy/legacy/index.js
Normal file
40
legacy/legacy/index.js
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const ytdl = require('ytdl-core');
|
||||||
|
|
||||||
|
let videos = [];
|
||||||
|
|
||||||
|
if (!fs.existsSync('./videos.txt')) {
|
||||||
|
console.log('Could not find videos.txt, make sure it exists and has youtube links, each on new lines');
|
||||||
|
process.exit();
|
||||||
|
}
|
||||||
|
|
||||||
|
let text = fs.readFileSync('./videos.txt').toString();
|
||||||
|
text.split('\n').forEach((line) => {
|
||||||
|
videos.push(line.replace(/\r/, ''));
|
||||||
|
});
|
||||||
|
|
||||||
|
videos.forEach((url) => {
|
||||||
|
if (ytdl.validateURL(url)) {
|
||||||
|
ytdl.getInfo(url, (err, info) => {
|
||||||
|
if (err) {
|
||||||
|
console.log(`An error occured while downloading '${url}'`)
|
||||||
|
} else {
|
||||||
|
let title = info.title;
|
||||||
|
|
||||||
|
try {
|
||||||
|
let stream = ytdl(url, {quality: 'highest'}).pipe(fs.createWriteStream(`${title}.mp4`));
|
||||||
|
stream.on('finish', () => console.log(`Finish downloading ${title}`));
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`An error occured while downloaing '${title}' retrying`)
|
||||||
|
try {
|
||||||
|
let stream = ytdl(url, {quality: 'highest'}).pipe(fs.createWriteStream(`${title}.mp4`));
|
||||||
|
stream.on('finish', () => console.log(`Finished downloading '${title}'`));
|
||||||
|
} catch (e) {
|
||||||
|
console.log(`Unable to download '${title}'`)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(`Downloading '${title}'`);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} else console.log(`Video ${url} was not found`);
|
||||||
|
});
|
||||||
15
legacy/legacy/package.json
Normal file
15
legacy/legacy/package.json
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
{
|
||||||
|
"name": "youtube-dowloader",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"author": "Ben (plane000)",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"fs": "0.0.1-security",
|
||||||
|
"ytdl-core": "^0.21.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
47
legacy/package-lock.json
generated
47
legacy/package-lock.json
generated
@@ -1,47 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "youtube-dowloader",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"lockfileVersion": 1,
|
|
||||||
"requires": true,
|
|
||||||
"dependencies": {
|
|
||||||
"fs": {
|
|
||||||
"version": "0.0.1-security",
|
|
||||||
"resolved": "https://registry.npmjs.org/fs/-/fs-0.0.1-security.tgz",
|
|
||||||
"integrity": "sha1-invTcYa23d84E/I4WLV+yq9eQdQ="
|
|
||||||
},
|
|
||||||
"html-entities": {
|
|
||||||
"version": "1.2.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/html-entities/-/html-entities-1.2.1.tgz",
|
|
||||||
"integrity": "sha1-DfKTUfByEWNRXfueVUPl9u7VFi8="
|
|
||||||
},
|
|
||||||
"m3u8stream": {
|
|
||||||
"version": "0.3.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.3.0.tgz",
|
|
||||||
"integrity": "sha512-0tvjXDIa6BolPEGo9zioQiPqfQhjopZXN3L7vZH/rZQCOLd4rPXNZc1UBMdW3TRpjNBoD0+F1X41/f0iY23rlQ==",
|
|
||||||
"requires": {
|
|
||||||
"miniget": "1.2.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"miniget": {
|
|
||||||
"version": "1.2.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/miniget/-/miniget-1.2.0.tgz",
|
|
||||||
"integrity": "sha1-ADY3Oia71S2+aUX85sjAOR6eEkE="
|
|
||||||
},
|
|
||||||
"sax": {
|
|
||||||
"version": "1.2.4",
|
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz",
|
|
||||||
"integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw=="
|
|
||||||
},
|
|
||||||
"ytdl-core": {
|
|
||||||
"version": "0.21.1",
|
|
||||||
"resolved": "https://registry.npmjs.org/ytdl-core/-/ytdl-core-0.21.1.tgz",
|
|
||||||
"integrity": "sha512-K6ysXuHW0X3MgeLpO4IHvHp5gdF2DDrgFPP7kQODBSpeaq4+/y8lvWy6ZpxtJdQGE0+GUaBZ2H1/kYcP327UdQ==",
|
|
||||||
"requires": {
|
|
||||||
"html-entities": "1.2.1",
|
|
||||||
"m3u8stream": "0.3.0",
|
|
||||||
"miniget": "1.2.0",
|
|
||||||
"sax": "1.2.4"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-dowloader",
|
"name": "youtube-downloader",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "",
|
||||||
"main": "index.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"repository": {
|
||||||
"test": "echo \"Error: no test specified\" && exit 1"
|
"type": "git",
|
||||||
|
"url": "git+https://github.com/plane000/youtube-downloader.git"
|
||||||
},
|
},
|
||||||
"author": "Ben (plane000)",
|
"author": "Ben (plane000)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"devDependencies": {},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fs": "0.0.1-security",
|
"express": "^4.16.4",
|
||||||
"ytdl-core": "^0.21.1"
|
"moment": "^2.23.0",
|
||||||
|
"socket.io": "^2.2.0",
|
||||||
|
"ytdl-core": "^0.28.3"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
legacy/public/index.html
Normal file
36
legacy/public/index.html
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<title>Ben's YouTube Downloader</title>
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<link rel="stylesheet" type="text/css" media="screen" href="style.css" />
|
||||||
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="Instructions">
|
||||||
|
<h1 id="CurrentDirectory">Currently saving videos to: <code id="saveloc">./</code></h1>
|
||||||
|
|
||||||
|
Videos to record (seperate different videos by new lines):
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="Input">
|
||||||
|
<textarea id="VideosToRecord"></textarea>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<button id="Download">Download</button>
|
||||||
|
<input type="checkbox" id="AudioOnly">Only Download Audio (.mp3)
|
||||||
|
|
||||||
|
<div id="VideoContainer">
|
||||||
|
<div id="VideoBox"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="Updater">
|
||||||
|
<button id="Update">Update</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script src="index.js"></script>
|
||||||
|
<div id="copyright">Copyright Benjamin Kyd© <script type="text/javascript">document.write(new Date().getFullYear())</script></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
98
legacy/public/index.js
Normal file
98
legacy/public/index.js
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
let socket = io();
|
||||||
|
|
||||||
|
(() => {
|
||||||
|
console.log('Starting up');
|
||||||
|
})();
|
||||||
|
|
||||||
|
socket.on('path', async (data) => {
|
||||||
|
document.getElementById('saveloc').innerText = data;
|
||||||
|
});
|
||||||
|
|
||||||
|
let isDownloading = false;
|
||||||
|
let VideosToDownload = {};
|
||||||
|
document.getElementById('VideosToRecord').addEventListener('keyup', (e) => {
|
||||||
|
if (isDownloading) return;
|
||||||
|
let current = document.getElementById('VideosToRecord').value;
|
||||||
|
VideosToDownload = current.split('\n');
|
||||||
|
|
||||||
|
let payload = {
|
||||||
|
ExpectPreview: true,
|
||||||
|
VideosToDownload: VideosToDownload
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('video-list', payload);
|
||||||
|
});
|
||||||
|
|
||||||
|
let VideoPreview = [];
|
||||||
|
|
||||||
|
function renderPreview() {
|
||||||
|
if (isDownloading) return;
|
||||||
|
document.getElementById('VideoBox').innerText = '';
|
||||||
|
for (const [key, value] of Object.entries(VideoPreview)) {
|
||||||
|
if (document.getElementById(key) == null) {
|
||||||
|
if (!value.found) {
|
||||||
|
document.getElementById('VideoBox').innerHTML += `<div id="${key}">${key}: <h>Video not found</h></div>`;
|
||||||
|
} else {
|
||||||
|
document.getElementById('VideoBox').innerHTML += `<div id="${key}">${key}: <h>${value.title}</h></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearPreview() {
|
||||||
|
document.getElementById('VideoBox').innerText = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('video-preview', async (data) => {
|
||||||
|
if (isDownloading) return;
|
||||||
|
if (!data || !data.data || data.contents == false) {
|
||||||
|
clearPreview();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
VideoPreview = data.data;
|
||||||
|
renderPreview();
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('Download').addEventListener('click', async (event) => {
|
||||||
|
if (isDownloading) return;
|
||||||
|
socket.emit('download', {
|
||||||
|
videos: document.getElementById('VideosToRecord').value.split('\n'),
|
||||||
|
audioOnly: document.getElementById('AudioOnly').checked
|
||||||
|
});
|
||||||
|
document.getElementById('VideoBox').innerText = 'Downloading...';
|
||||||
|
// document.getElementById('VideosToRecord').value = null;
|
||||||
|
isDownloading = true;
|
||||||
|
console.log('Asked server for download...');
|
||||||
|
});
|
||||||
|
|
||||||
|
let downloads = [];
|
||||||
|
let downloadCount = 0;
|
||||||
|
let completedDownloads = 0;
|
||||||
|
|
||||||
|
function renderDownloads() {
|
||||||
|
document.getElementById('VideoBox').innerText = '';
|
||||||
|
for (const [key, value] of Object.entries(downloads)) {
|
||||||
|
document.getElementById('VideoBox').innerHTML += `<div id="${key}">${value.title}: <h>${value.percent}</h></div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.on('download-count', async (data) => {
|
||||||
|
downloadCount = data.num;
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('download-done', async(data) => {
|
||||||
|
downloads[data.video] = {title: data.title, percent: 'Complete!'};
|
||||||
|
renderDownloads();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('download-progress', async (data) => {
|
||||||
|
downloads[data.video] = data;
|
||||||
|
renderDownloads();
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('queue-concluded', async (data) => {
|
||||||
|
completedDownloads = 0; downloadCount = 0;
|
||||||
|
isDownloading = false;
|
||||||
|
downloads = [];
|
||||||
|
document.getElementById('VideoBox').innerHTML += "<h>Queue Concluded...</h>";
|
||||||
|
});
|
||||||
|
Before Width: | Height: | Size: 29 KiB After Width: | Height: | Size: 29 KiB |
13
legacy/src/index.js
Normal file
13
legacy/src/index.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
const server = require('./server');
|
||||||
|
|
||||||
|
let config = {
|
||||||
|
serverPort: 8080,
|
||||||
|
downloadLocation: './'
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports.config = config;
|
||||||
|
|
||||||
|
module.exports.main = async () => {
|
||||||
|
await server.init();
|
||||||
|
await server.listen();
|
||||||
|
}
|
||||||
49
legacy/src/server.js
Normal file
49
legacy/src/server.js
Normal file
@@ -0,0 +1,49 @@
|
|||||||
|
const logger = require('./logger')
|
||||||
|
const main = require('./index');
|
||||||
|
const youtube = require('./youtubehelper');
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
|
||||||
|
let app;
|
||||||
|
let http;
|
||||||
|
let io;
|
||||||
|
|
||||||
|
module.exports.init = async () => {
|
||||||
|
app = require('express')();
|
||||||
|
http = require('http').Server(app);
|
||||||
|
io = require('socket.io')(http);
|
||||||
|
|
||||||
|
http.listen(main.config.serverPort, () => {
|
||||||
|
logger.log(`HTTP server listening on port ${main.config.serverPort}`);
|
||||||
|
logger.log(`WebSocket server listening on ${main.config.serverPort}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.listen = async () => {
|
||||||
|
app.use(express.static('public'));
|
||||||
|
|
||||||
|
io.on('connection', async (socket) => {
|
||||||
|
logger.log(`New socket connection from id: ${socket.id}`);
|
||||||
|
|
||||||
|
socket.emit('path', main.config.downloadLocation);
|
||||||
|
|
||||||
|
socket.on('video-list', async (data) => {
|
||||||
|
if (data.ExpectPreview) {
|
||||||
|
logger.log(`Socket id '${socket.id}' is requesting a video preview`);
|
||||||
|
let response = await youtube.resolveVideos(data.VideosToDownload);
|
||||||
|
socket.emit('video-preview', response);
|
||||||
|
logger.log(`Finished preview for socket id '${socket.id}'`);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('download', async (data) => {
|
||||||
|
logger.log(`Socket id '${socket.id}' is requesting a download`);
|
||||||
|
let toDownload = await youtube.resolveVideos(data.videos);
|
||||||
|
youtube.setupDownloadQueue(toDownload.data, socket, {path: main.config.downloadLocation, audioOnly: !data.audioOnly});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.app = app;
|
||||||
|
module.exports.http = http;
|
||||||
|
module.exports.io = io;
|
||||||
124
legacy/src/youtubehelper.js
Normal file
124
legacy/src/youtubehelper.js
Normal file
@@ -0,0 +1,124 @@
|
|||||||
|
const logger = require('./logger');
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const ytdl = require('ytdl-core');
|
||||||
|
|
||||||
|
module.exports.resolveVideos = async (arr) => {
|
||||||
|
let output = {contents: false, data: {}};
|
||||||
|
|
||||||
|
for (let video of arr) {
|
||||||
|
if (video == '' || video == ' ') continue;
|
||||||
|
try {
|
||||||
|
if (await ytdl.validateURL(video)) {
|
||||||
|
const info = await ytdl.getBasicInfo(video);
|
||||||
|
if (!info) {
|
||||||
|
output.data[video] = {found: false};
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
output.data[video] = {
|
||||||
|
found: true,
|
||||||
|
error: false,
|
||||||
|
title: info.title,
|
||||||
|
thumbnail: info.thumbnail_url.replace('default', 'maxresdefault'),
|
||||||
|
runtime: info.length_seconds
|
||||||
|
}
|
||||||
|
output.contents = true;
|
||||||
|
} else {
|
||||||
|
output.data[video] = {found: false, error: true};
|
||||||
|
output.contents = true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
if (!e.toString().includes('Error: This video contains content')) {
|
||||||
|
output.data[video] = {found: false};
|
||||||
|
output.contents = true;
|
||||||
|
logger.log(`Error resolving video ${video}: ${e}`);
|
||||||
|
} else {
|
||||||
|
output.data[video] = {
|
||||||
|
found: true,
|
||||||
|
error: true,
|
||||||
|
title: 'Not found: This video is blocked in your country',
|
||||||
|
thumbnail: 'ERRORED',
|
||||||
|
runtime: 'NONE'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
let downloadQueue = [];
|
||||||
|
|
||||||
|
module.exports.setupDownloadQueue = async (arr, socket, options) => {
|
||||||
|
let numOfDownloads;
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(arr))
|
||||||
|
if (value.error) delete arr[key];
|
||||||
|
|
||||||
|
downloadQueue[socket.id] = {
|
||||||
|
count: 0,
|
||||||
|
videos: { }
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const [key, value] of Object.entries(arr))
|
||||||
|
if (ytdl.validateURL(key))
|
||||||
|
socket.emit('download-progress', {video: key, percent: "Queued", title: value.title});
|
||||||
|
|
||||||
|
await new Promise(async (resolve, reject) => {
|
||||||
|
for (const [key, value] of Object.entries(arr)) {
|
||||||
|
if (ytdl.validateURL(key)) {
|
||||||
|
await runQueueAsync(socket.id);
|
||||||
|
await download(key, value.title, socket, options.audioOnly);
|
||||||
|
}
|
||||||
|
// if (arr.indexOf(key) == arr.length) {resolve();}
|
||||||
|
}
|
||||||
|
resolve();
|
||||||
|
});
|
||||||
|
socket.emit('queue-concluded');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function runQueueAsync(socketID) {
|
||||||
|
// Ready for proper queueuing
|
||||||
|
}
|
||||||
|
|
||||||
|
async function download(video, videoName ,socket, audioOnly, path = './') {
|
||||||
|
return new Promise(async (resolve, reject) => {
|
||||||
|
let stream;
|
||||||
|
try {
|
||||||
|
if (audioOnly) {
|
||||||
|
stream = await ytdl(video, {quality: 'highest', filter: (format) => format.container === 'mp4'});
|
||||||
|
stream.pipe(fs.createWriteStream(`${path}/${(videoName).replace(/\//, '-')}.mp4`));
|
||||||
|
} else {
|
||||||
|
stream = await ytdl(video, {quality: 'highest', filter: "audioonly"});
|
||||||
|
stream.pipe(fs.createWriteStream(`${path}/${(videoName).replace(/\//, '-')}.mp3`));
|
||||||
|
}
|
||||||
|
|
||||||
|
stream.on('response', (res) => {
|
||||||
|
let totalSize = res.headers['content-length'];
|
||||||
|
let dataRead = 0;
|
||||||
|
let lastPercent = 0;
|
||||||
|
res.on('data', (data) => {
|
||||||
|
dataRead += data.length;
|
||||||
|
let percent = Math.floor((dataRead / totalSize) * 100) + '%';
|
||||||
|
if (percent != lastPercent) {
|
||||||
|
socket.emit('download-progress', {video: video, percent: percent, title: videoName});
|
||||||
|
}
|
||||||
|
lastPercent = percent;
|
||||||
|
});
|
||||||
|
|
||||||
|
res.on('end', () => {
|
||||||
|
logger.log(`Socket id '${socket.id}' finished downloading ${videoName}`)
|
||||||
|
socket.emit('download-done', {video: video, title: videoName});
|
||||||
|
resolve('complete');
|
||||||
|
});
|
||||||
|
|
||||||
|
logger.log(`Socket id '${socket.id}' is downloading ${videoName}`);
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
logger.log(`Socket id '${socket.id}' failed to download ${videoName}: ${e}`);
|
||||||
|
socket.emit('download-done', {video: video, title: videoName});
|
||||||
|
socket.emit('download-progress', {video: video, percent: "Failed", title: videoName});
|
||||||
|
resolve('error');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
22
package.json
22
package.json
@@ -1,19 +1,25 @@
|
|||||||
{
|
{
|
||||||
"name": "youtube-downloader",
|
"name": "youtube-downloader",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "",
|
"description": "Simple to use youtube downloader, simply run the index javascript file with node and then go to localhost on your browser, by default the video will download to this folder but can be changed in src/index.js",
|
||||||
"main": "main.js",
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git+https://github.com/plane000/youtube-downloader.git"
|
"url": "git+https://github.com/plane000/youtube-downloader.git"
|
||||||
},
|
},
|
||||||
"author": "Ben (plane000)",
|
"author": "Ben (benjaminkyd@gmail.com)",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {},
|
"bugs": {
|
||||||
|
"url": "https://github.com/plane000/youtube-downloader/issues"
|
||||||
|
},
|
||||||
|
"homepage": "https://github.com/plane000/youtube-downloader#readme",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.16.4",
|
"express": "^4.17.1",
|
||||||
"moment": "^2.23.0",
|
"moment": "^2.26.0",
|
||||||
"socket.io": "^2.2.0",
|
"socket.io": "^2.3.0",
|
||||||
"ytdl-core": "^0.28.3"
|
"ytdl-core": "^2.1.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,15 +3,13 @@
|
|||||||
<head>
|
<head>
|
||||||
<meta charset="utf-8" />
|
<meta charset="utf-8" />
|
||||||
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
<title>Ben's YouTube Downloader</title>
|
<title>Ben's Amazing YouTube Downloader</title>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<link rel="stylesheet" type="text/css" media="screen" href="style.css" />
|
<link rel="stylesheet" type="text/css" media="screen" href="style.css" />
|
||||||
<script src="/socket.io/socket.io.js"></script>
|
<script src="/socket.io/socket.io.js"></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="Instructions">
|
<div id="Instructions">
|
||||||
<h1 id="CurrentDirectory">Currently saving videos to: <code id="saveloc">./</code></h1>
|
|
||||||
|
|
||||||
Videos to record (seperate different videos by new lines):
|
Videos to record (seperate different videos by new lines):
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -19,17 +17,12 @@
|
|||||||
<textarea id="VideosToRecord"></textarea>
|
<textarea id="VideosToRecord"></textarea>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<button id="Download">Download</button>
|
<div id="ResolvedVideoContainer"></div>
|
||||||
<input type="checkbox" id="AudioOnly">Only Download Audio (.mp3)
|
|
||||||
|
|
||||||
<div id="VideoContainer">
|
<!-- <button id="Download">Download</button> -->
|
||||||
<div id="VideoBox"></div>
|
<!-- <input type="checkbox" id="AudioOnly">Only Download Audio (.mp3) -->
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="Updater">
|
|
||||||
<button id="Update">Update</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
<script src="youtubedombuilder.js"></script>
|
||||||
<script src="index.js"></script>
|
<script src="index.js"></script>
|
||||||
<div id="copyright">Copyright Benjamin Kyd© <script type="text/javascript">document.write(new Date().getFullYear())</script></div>
|
<div id="copyright">Copyright Benjamin Kyd© <script type="text/javascript">document.write(new Date().getFullYear())</script></div>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
134
public/index.js
134
public/index.js
@@ -1,98 +1,36 @@
|
|||||||
let socket = io();
|
let Socket = io();
|
||||||
|
|
||||||
(() => {
|
(() =>
|
||||||
console.log('Starting up');
|
{
|
||||||
})();
|
console.log('Starting up');
|
||||||
|
})();
|
||||||
socket.on('path', async (data) => {
|
|
||||||
document.getElementById('saveloc').innerText = data;
|
function DownloadButtonPressed(format, id)
|
||||||
});
|
{
|
||||||
|
alert(`${format} ${id}`);
|
||||||
let isDownloading = false;
|
}
|
||||||
let VideosToDownload = {};
|
|
||||||
document.getElementById('VideosToRecord').addEventListener('keyup', (e) => {
|
const VideoInput = document.getElementById('VideosToRecord');
|
||||||
if (isDownloading) return;
|
|
||||||
let current = document.getElementById('VideosToRecord').value;
|
VideoInput.oninput = () =>
|
||||||
VideosToDownload = current.split('\n');
|
{
|
||||||
|
const ToSend = {
|
||||||
let payload = {
|
// TODO: add toggle
|
||||||
ExpectPreview: true,
|
ExpectPreview: true,
|
||||||
VideosToDownload: VideosToDownload
|
Content: VideoInput.value.split('\n')
|
||||||
};
|
};
|
||||||
|
Socket.emit('VideoListUpdate', ToSend);
|
||||||
socket.emit('video-list', payload);
|
};
|
||||||
});
|
|
||||||
|
Socket.on('VideoListResolution', (res) =>
|
||||||
let VideoPreview = [];
|
{
|
||||||
|
if (res.Error === true) return;
|
||||||
function renderPreview() {
|
ResolvedBasic(res.Content);
|
||||||
if (isDownloading) return;
|
});
|
||||||
document.getElementById('VideoBox').innerText = '';
|
|
||||||
for (const [key, value] of Object.entries(VideoPreview)) {
|
Socket.on('VideoListResolved', (res) =>
|
||||||
if (document.getElementById(key) == null) {
|
{
|
||||||
if (!value.found) {
|
console.log(res);
|
||||||
document.getElementById('VideoBox').innerHTML += `<div id="${key}">${key}: <h>Video not found</h></div>`;
|
if (res.Error === true) return;
|
||||||
} else {
|
ResolvedVideos(res.Content);
|
||||||
document.getElementById('VideoBox').innerHTML += `<div id="${key}">${key}: <h>${value.title}</h></div>`;
|
});
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function clearPreview() {
|
|
||||||
document.getElementById('VideoBox').innerText = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on('video-preview', async (data) => {
|
|
||||||
if (isDownloading) return;
|
|
||||||
if (!data || !data.data || data.contents == false) {
|
|
||||||
clearPreview();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
VideoPreview = data.data;
|
|
||||||
renderPreview();
|
|
||||||
});
|
|
||||||
|
|
||||||
document.getElementById('Download').addEventListener('click', async (event) => {
|
|
||||||
if (isDownloading) return;
|
|
||||||
socket.emit('download', {
|
|
||||||
videos: document.getElementById('VideosToRecord').value.split('\n'),
|
|
||||||
audioOnly: document.getElementById('AudioOnly').checked
|
|
||||||
});
|
|
||||||
document.getElementById('VideoBox').innerText = 'Downloading...';
|
|
||||||
// document.getElementById('VideosToRecord').value = null;
|
|
||||||
isDownloading = true;
|
|
||||||
console.log('Asked server for download...');
|
|
||||||
});
|
|
||||||
|
|
||||||
let downloads = [];
|
|
||||||
let downloadCount = 0;
|
|
||||||
let completedDownloads = 0;
|
|
||||||
|
|
||||||
function renderDownloads() {
|
|
||||||
document.getElementById('VideoBox').innerText = '';
|
|
||||||
for (const [key, value] of Object.entries(downloads)) {
|
|
||||||
document.getElementById('VideoBox').innerHTML += `<div id="${key}">${value.title}: <h>${value.percent}</h></div>`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
socket.on('download-count', async (data) => {
|
|
||||||
downloadCount = data.num;
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('download-done', async(data) => {
|
|
||||||
downloads[data.video] = {title: data.title, percent: 'Complete!'};
|
|
||||||
renderDownloads();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('download-progress', async (data) => {
|
|
||||||
downloads[data.video] = data;
|
|
||||||
renderDownloads();
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('queue-concluded', async (data) => {
|
|
||||||
completedDownloads = 0; downloadCount = 0;
|
|
||||||
isDownloading = false;
|
|
||||||
downloads = [];
|
|
||||||
document.getElementById('VideoBox').innerHTML += "<h>Queue Concluded...</h>";
|
|
||||||
});
|
|
||||||
|
|||||||
@@ -34,19 +34,42 @@ h {
|
|||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
}
|
}
|
||||||
|
|
||||||
#VideoContainer {
|
.table {
|
||||||
padding: 30px, 0px, 30px, 0px;
|
width: 60%;
|
||||||
}
|
}
|
||||||
|
|
||||||
#VideoBox {
|
.thumbnail {
|
||||||
padding-top: 10px;
|
max-width: 150%;
|
||||||
font-weight: lighter;
|
height: auto;
|
||||||
}
|
}
|
||||||
|
|
||||||
#Updater {
|
.videotitle {
|
||||||
position: absolute;
|
font-size: 1rem;
|
||||||
top: 5px;
|
}
|
||||||
right: 10px;}
|
|
||||||
|
.channel {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.views {
|
||||||
|
font-size: 0.9rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.desc {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
display: inline-block;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow: hidden;
|
||||||
|
line-height: 1.2em;
|
||||||
|
max-height: 2.4em;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.downloadbuttons {
|
||||||
|
font-size: 0.8rem;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
#copyright {
|
#copyright {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
|
|||||||
103
public/youtubedombuilder.js
Normal file
103
public/youtubedombuilder.js
Normal file
@@ -0,0 +1,103 @@
|
|||||||
|
let VideoList = []; // Array indexed by video ID
|
||||||
|
|
||||||
|
const ResolvedVideoContainer = document.getElementById('ResolvedVideoContainer');
|
||||||
|
|
||||||
|
function CleanUp()
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResolvingElement(id, url, status, error = 'An unknown error occured')
|
||||||
|
{
|
||||||
|
console.log(status);
|
||||||
|
if (status == 'Resolving')
|
||||||
|
return `<div id="${id}"> URL: ${url} <br>Status: Resolving...</div><br>`
|
||||||
|
if (status == 'Error')
|
||||||
|
return `<div id="${id}"> URL: ${url} <br>Status: Error, ${error}</div><br>`
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResolvedBasic(BasicResolution)
|
||||||
|
{
|
||||||
|
for (res of BasicResolution)
|
||||||
|
{
|
||||||
|
// url is not valid
|
||||||
|
if (res.id == -1)
|
||||||
|
{
|
||||||
|
console.log(`Video: ${res.url} will not be resolved`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VideoList[res.id]) continue;
|
||||||
|
|
||||||
|
const action = res.action;
|
||||||
|
ResolvedVideoContainer.innerHTML += ResolvingElement(res.id, res.url, action)
|
||||||
|
const htmlElement = document.getElementById(res.id);
|
||||||
|
|
||||||
|
VideoList[res.id] = {
|
||||||
|
id: res.id,
|
||||||
|
element: htmlElement,
|
||||||
|
status: res.action
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function VideoElement(id, title, desc, views, thumbnail, channel, runtime)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
// format views so theres commas
|
||||||
|
// in it ennit - Will is a stupid
|
||||||
|
// "Fuck you, of course it's a string" - b_boy_ww 2020
|
||||||
|
views = parseInt(views);
|
||||||
|
views = views.toLocaleString('en-US');
|
||||||
|
|
||||||
|
return `
|
||||||
|
<table class="table">
|
||||||
|
<tr>
|
||||||
|
<td rowspan="4" class="thumbnail"><a href="https://www.youtube.com/watch?v=${id}"><img src="${thumbnail}"></a></td>
|
||||||
|
<td colspan="4" class="videotitle">${title}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td class="channel">${channel}</td>
|
||||||
|
<td class="views">${views} views</td>
|
||||||
|
<td></td>
|
||||||
|
<td></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="4" class="desc">${desc}</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td colspan="2" class="downloadbuttons"><button onclick="DownloadButtonPressed('MP4(MAX)', ${id})">Download Video</button> </td>
|
||||||
|
<td colspan="2" class="downloadbuttons"><button onclick="DownloadButtonPressed('MP3(MAX)', ${id})">Download Audio</button> </td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
function ResolvedVideos(VideoResolution)
|
||||||
|
{
|
||||||
|
for (res of VideoResolution)
|
||||||
|
{
|
||||||
|
if (!VideoList[res.id])
|
||||||
|
{
|
||||||
|
console.log(`Unexpected video, ${res.id} was not in the resolve queue`)
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (VideoList[res.id].status == "Resolved") continue;
|
||||||
|
|
||||||
|
const Video = VideoList[res.id];
|
||||||
|
const htmlElement = Video.element;
|
||||||
|
|
||||||
|
if (res.Error == true)
|
||||||
|
{
|
||||||
|
// there is no url element ! it is discarded on the server in place for an ID
|
||||||
|
htmlElement.innerHTML = ResolvingElement(res.id, `https://www.youtube.com/watch?v=${res.id}`, 'Error', res.Errors[0]);
|
||||||
|
VideoList[res.id].status = "Error";
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
htmlElement.innerHTML = VideoElement(res.id, res.title, res.desc, res.views, res.thumbnail, res.channel, res.runtime);
|
||||||
|
VideoList[res.id].status = "Resolved";
|
||||||
|
}
|
||||||
|
}
|
||||||
16
src/config.js
Normal file
16
src/config.js
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
const Logger = require('./logger');
|
||||||
|
|
||||||
|
module.exports.Configuration = {}
|
||||||
|
|
||||||
|
module.exports.Load = () =>
|
||||||
|
{
|
||||||
|
this.Configuration = {
|
||||||
|
LogFile: 'logs.log',
|
||||||
|
ListenPort: 8080,
|
||||||
|
PublicDirectory: 'public',
|
||||||
|
StorageDirectory: './tmp/',
|
||||||
|
CacheCleanInterval: 300000, // 5 mins
|
||||||
|
CacheTimeToUse: 600000, // 10 mins
|
||||||
|
}
|
||||||
|
Logger.Log('Configuration loaded');
|
||||||
|
}
|
||||||
512
src/formats.js
Normal file
512
src/formats.js
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
module.exports = {
|
||||||
|
5: {
|
||||||
|
mimeType: 'video/flv; codecs="Sorenson H.283, mp3"',
|
||||||
|
qualityLabel: '240p',
|
||||||
|
bitrate: 250000,
|
||||||
|
audioBitrate: 64,
|
||||||
|
},
|
||||||
|
|
||||||
|
6: {
|
||||||
|
mimeType: 'video/flv; codecs="Sorenson H.263, mp3"',
|
||||||
|
qualityLabel: '270p',
|
||||||
|
bitrate: 800000,
|
||||||
|
audioBitrate: 64,
|
||||||
|
},
|
||||||
|
|
||||||
|
13: {
|
||||||
|
mimeType: 'video/3gp; codecs="MPEG-4 Visual, aac"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
17: {
|
||||||
|
mimeType: 'video/3gp; codecs="MPEG-4 Visual, aac"',
|
||||||
|
qualityLabel: '144p',
|
||||||
|
bitrate: 50000,
|
||||||
|
audioBitrate: 24,
|
||||||
|
},
|
||||||
|
|
||||||
|
18: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '360p',
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: 96,
|
||||||
|
},
|
||||||
|
|
||||||
|
22: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: 2000000,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
34: {
|
||||||
|
mimeType: 'video/flv; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '360p',
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
35: {
|
||||||
|
mimeType: 'video/flv; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '480p',
|
||||||
|
bitrate: 800000,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
36: {
|
||||||
|
mimeType: 'video/3gp; codecs="MPEG-4 Visual, aac"',
|
||||||
|
qualityLabel: '240p',
|
||||||
|
bitrate: 175000,
|
||||||
|
audioBitrate: 32,
|
||||||
|
},
|
||||||
|
|
||||||
|
37: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '1080p',
|
||||||
|
bitrate: 3000000,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
38: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '3072p',
|
||||||
|
bitrate: 3500000,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
43: {
|
||||||
|
mimeType: 'video/webm; codecs="VP8, vorbis"',
|
||||||
|
qualityLabel: '360p',
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
44: {
|
||||||
|
mimeType: 'video/webm; codecs="VP8, vorbis"',
|
||||||
|
qualityLabel: '480p',
|
||||||
|
bitrate: 1000000,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
45: {
|
||||||
|
mimeType: 'video/webm; codecs="VP8, vorbis"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: 2000000,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
46: {
|
||||||
|
mimeType: 'audio/webm; codecs="vp8, vorbis"',
|
||||||
|
qualityLabel: '1080p',
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
82: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '360p',
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: 96,
|
||||||
|
},
|
||||||
|
|
||||||
|
83: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '240p',
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: 96,
|
||||||
|
},
|
||||||
|
|
||||||
|
84: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: 2000000,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
85: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '1080p',
|
||||||
|
bitrate: 3000000,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
91: {
|
||||||
|
mimeType: 'video/ts; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '144p',
|
||||||
|
bitrate: 100000,
|
||||||
|
audioBitrate: 48,
|
||||||
|
},
|
||||||
|
|
||||||
|
92: {
|
||||||
|
mimeType: 'video/ts; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '240p',
|
||||||
|
bitrate: 150000,
|
||||||
|
audioBitrate: 48,
|
||||||
|
},
|
||||||
|
|
||||||
|
93: {
|
||||||
|
mimeType: 'video/ts; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '360p',
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
94: {
|
||||||
|
mimeType: 'video/ts; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '480p',
|
||||||
|
bitrate: 800000,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
95: {
|
||||||
|
mimeType: 'video/ts; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: 1500000,
|
||||||
|
audioBitrate: 256,
|
||||||
|
},
|
||||||
|
|
||||||
|
96: {
|
||||||
|
mimeType: 'video/ts; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '1080p',
|
||||||
|
bitrate: 2500000,
|
||||||
|
audioBitrate: 256,
|
||||||
|
},
|
||||||
|
|
||||||
|
100: {
|
||||||
|
mimeType: 'audio/webm; codecs="VP8, vorbis"',
|
||||||
|
qualityLabel: '360p',
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
101: {
|
||||||
|
mimeType: 'audio/webm; codecs="VP8, vorbis"',
|
||||||
|
qualityLabel: '360p',
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
102: {
|
||||||
|
mimeType: 'audio/webm; codecs="VP8, vorbis"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
120: {
|
||||||
|
mimeType: 'video/flv; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: 2000000,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
127: {
|
||||||
|
mimeType: 'audio/ts; codecs="aac"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 96,
|
||||||
|
},
|
||||||
|
|
||||||
|
128: {
|
||||||
|
mimeType: 'audio/ts; codecs="aac"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 96,
|
||||||
|
},
|
||||||
|
|
||||||
|
132: {
|
||||||
|
mimeType: 'video/ts; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '240p',
|
||||||
|
bitrate: 150000,
|
||||||
|
audioBitrate: 48,
|
||||||
|
},
|
||||||
|
|
||||||
|
133: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '240p',
|
||||||
|
bitrate: 200000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
134: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '360p',
|
||||||
|
bitrate: 300000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
135: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '480p',
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
136: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: 1000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
137: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '1080p',
|
||||||
|
bitrate: 2500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
138: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '4320p',
|
||||||
|
bitrate: 13500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
139: {
|
||||||
|
mimeType: 'audio/mp4; codecs="aac"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 48,
|
||||||
|
},
|
||||||
|
|
||||||
|
140: {
|
||||||
|
mimeType: 'audio/m4a; codecs="aac"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
141: {
|
||||||
|
mimeType: 'audio/mp4; codecs="aac"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 256,
|
||||||
|
},
|
||||||
|
|
||||||
|
151: {
|
||||||
|
mimeType: 'video/ts; codecs="H.264, aac"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: 50000,
|
||||||
|
audioBitrate: 24,
|
||||||
|
},
|
||||||
|
|
||||||
|
160: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '144p',
|
||||||
|
bitrate: 100000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
171: {
|
||||||
|
mimeType: 'audio/webm; codecs="vorbis"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 128,
|
||||||
|
},
|
||||||
|
|
||||||
|
172: {
|
||||||
|
mimeType: 'audio/webm; codecs="vorbis"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 192,
|
||||||
|
},
|
||||||
|
|
||||||
|
242: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '240p',
|
||||||
|
bitrate: 100000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
243: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '360p',
|
||||||
|
bitrate: 250000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
244: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '480p',
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
247: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: 700000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
248: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '1080p',
|
||||||
|
bitrate: 1500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
249: {
|
||||||
|
mimeType: 'audio/webm; codecs="opus"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 48,
|
||||||
|
},
|
||||||
|
|
||||||
|
250: {
|
||||||
|
mimeType: 'audio/webm; codecs="opus"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 64,
|
||||||
|
},
|
||||||
|
|
||||||
|
251: {
|
||||||
|
mimeType: 'audio/webm; codecs="opus"',
|
||||||
|
qualityLabel: null,
|
||||||
|
bitrate: null,
|
||||||
|
audioBitrate: 160,
|
||||||
|
},
|
||||||
|
|
||||||
|
264: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '1440p',
|
||||||
|
bitrate: 4000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
266: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '2160p',
|
||||||
|
bitrate: 12500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
271: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '1440p',
|
||||||
|
bitrate: 9000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
272: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '4320p',
|
||||||
|
bitrate: 20000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
278: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '144p 15fps',
|
||||||
|
bitrate: 80000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
298: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '720p',
|
||||||
|
bitrate: 3000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
299: {
|
||||||
|
mimeType: 'video/mp4; codecs="H.264"',
|
||||||
|
qualityLabel: '1080p',
|
||||||
|
bitrate: 5500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
302: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '720p HFR',
|
||||||
|
bitrate: 2500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
303: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '1080p HFR',
|
||||||
|
bitrate: 5000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
308: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '1440p HFR',
|
||||||
|
bitrate: 10000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
313: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '2160p',
|
||||||
|
bitrate: 13000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
315: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '2160p HFR',
|
||||||
|
bitrate: 20000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
330: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '144p HDR, HFR',
|
||||||
|
bitrate: 80000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
331: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '240p HDR, HFR',
|
||||||
|
bitrate: 100000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
332: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '360p HDR, HFR',
|
||||||
|
bitrate: 250000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
333: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '240p HDR, HFR',
|
||||||
|
bitrate: 500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
334: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '720p HDR, HFR',
|
||||||
|
bitrate: 1000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
335: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '1080p HDR, HFR',
|
||||||
|
bitrate: 1500000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
336: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '1440p HDR, HFR',
|
||||||
|
bitrate: 5000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
|
||||||
|
337: {
|
||||||
|
mimeType: 'video/webm; codecs="VP9"',
|
||||||
|
qualityLabel: '2160p HDR, HFR',
|
||||||
|
bitrate: 12000000,
|
||||||
|
audioBitrate: null,
|
||||||
|
},
|
||||||
|
};
|
||||||
20
src/index.js
20
src/index.js
@@ -1,13 +1,15 @@
|
|||||||
const server = require('./server');
|
const Logger = require('./logger')
|
||||||
|
const Config = require('./config');
|
||||||
|
const Server = require('./server');
|
||||||
|
|
||||||
let config = {
|
const YoutubeHelper = require('./youtubehelper');
|
||||||
serverPort: 8080,
|
|
||||||
downloadLocation: './'
|
|
||||||
};
|
|
||||||
|
|
||||||
module.exports.config = config;
|
module.exports.Main = async () =>
|
||||||
|
{
|
||||||
|
await Config.Load();
|
||||||
|
|
||||||
module.exports.main = async () => {
|
await YoutubeHelper.InitResolverCache();
|
||||||
await server.init();
|
|
||||||
await server.listen();
|
await Server.Init();
|
||||||
|
Server.Listen();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,14 @@
|
|||||||
const moment = require('moment');
|
const Config = require('./config')
|
||||||
|
|
||||||
|
const Moment = require('moment');
|
||||||
|
const fs = require('fs');
|
||||||
|
|
||||||
const dateFormat = 'DD-MM-YY HH:mm:ss'
|
const dateFormat = 'DD-MM-YY HH:mm:ss'
|
||||||
|
|
||||||
module.exports.log = function(log) {
|
module.exports.Log = (log) =>
|
||||||
let d = moment().format(dateFormat);
|
{
|
||||||
console.log('[' + d.toLocaleString() + '] ' + log);
|
let d = Moment().format(dateFormat);
|
||||||
|
log = '[' + d.toLocaleString() + '] ' + log;
|
||||||
|
console.log(log);
|
||||||
|
fs.appendFile(Config.Configuration.LogFile, log + '\n', (e) => { if (e) throw e; });
|
||||||
}
|
}
|
||||||
|
|||||||
158
src/server.js
158
src/server.js
@@ -1,6 +1,8 @@
|
|||||||
const logger = require('./logger')
|
const Logger = require('./logger')
|
||||||
const main = require('./index');
|
const Config = require('./config');
|
||||||
const youtube = require('./youtubehelper');
|
|
||||||
|
const YoutubeHelper = require('./youtubehelper');
|
||||||
|
const YoutubeDownloader = require('./youtubedownloader');
|
||||||
|
|
||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
|
||||||
@@ -8,42 +10,138 @@ let app;
|
|||||||
let http;
|
let http;
|
||||||
let io;
|
let io;
|
||||||
|
|
||||||
module.exports.init = async () => {
|
module.exports.Init = async () =>
|
||||||
|
{
|
||||||
app = require('express')();
|
app = require('express')();
|
||||||
http = require('http').Server(app);
|
http = require('http').Server(app);
|
||||||
io = require('socket.io')(http);
|
io = require('socket.io')(http);
|
||||||
|
|
||||||
http.listen(main.config.serverPort, () => {
|
|
||||||
logger.log(`HTTP server listening on port ${main.config.serverPort}`);
|
|
||||||
logger.log(`WebSocket server listening on ${main.config.serverPort}`);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.listen = async () => {
|
module.exports.Listen = async () =>
|
||||||
app.use(express.static('public'));
|
{
|
||||||
|
http.listen(Config.Configuration.ListenPort, () => {
|
||||||
|
Logger.Log(`HTTP server listening on port ${Config.Configuration.ListenPort}`);
|
||||||
|
Logger.Log(`WebSocket server listening on ${Config.Configuration.ListenPort}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(express.static(Config.Configuration.PublicDirectory));
|
||||||
|
|
||||||
io.on('connection', async (socket) => {
|
io.on('connection', async (socket) => {
|
||||||
logger.log(`New socket connection from id: ${socket.id}`);
|
Logger.Log(`New socket connection from ip: ${socket.handshake.address}, unique id: ${socket.id}`);
|
||||||
|
|
||||||
socket.emit('path', main.config.downloadLocation);
|
SocketHandler(socket);
|
||||||
|
|
||||||
socket.on('video-list', async (data) => {
|
|
||||||
if (data.ExpectPreview) {
|
|
||||||
logger.log(`Socket id '${socket.id}' is requesting a video preview`);
|
|
||||||
let response = await youtube.resolveVideos(data.VideosToDownload);
|
|
||||||
socket.emit('video-preview', response);
|
|
||||||
logger.log(`Finished preview for socket id '${socket.id}'`);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
socket.on('download', async (data) => {
|
|
||||||
logger.log(`Socket id '${socket.id}' is requesting a download`);
|
|
||||||
let toDownload = await youtube.resolveVideos(data.videos);
|
|
||||||
youtube.setupDownloadQueue(toDownload.data, socket, {path: main.config.downloadLocation, audioOnly: !data.audioOnly});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports.app = app;
|
function SocketHandler(socket)
|
||||||
module.exports.http = http;
|
{
|
||||||
module.exports.io = io;
|
socket.on('VideoListUpdate', (req) => VideoListUpdate(socket, req) );
|
||||||
|
}
|
||||||
|
|
||||||
|
async function VideoListUpdate(socket, req)
|
||||||
|
{
|
||||||
|
// req format
|
||||||
|
// {
|
||||||
|
// ExptectPreview: bool,
|
||||||
|
// Content: [arr]
|
||||||
|
// }
|
||||||
|
|
||||||
|
let Res = {};
|
||||||
|
|
||||||
|
// res format
|
||||||
|
// errors not present if error is false
|
||||||
|
// content not present if error is true
|
||||||
|
// {
|
||||||
|
// Error: bool,
|
||||||
|
// Errors: [err],
|
||||||
|
// Content: [{
|
||||||
|
// id: int,
|
||||||
|
// url: string,
|
||||||
|
// valid: bool,
|
||||||
|
// action: string
|
||||||
|
// }]
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (!req || !req.Content)
|
||||||
|
{
|
||||||
|
Res.Error = true;
|
||||||
|
Res.Errors = ErorrsBuilder("Content Error", "No content in request");
|
||||||
|
socket.emit('VideoListResolution', Res);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const VideoArray = req.Content;
|
||||||
|
|
||||||
|
const YoutubeRegex = /^((?:https?:)?\/\/)?((?:www|m)\.)?((?:youtube\.com|youtu.be))(\/(?:[\w\-]+\?v=|embed\/|v\/)?)([\w\-]+)(\S+)?$/;
|
||||||
|
|
||||||
|
let ResolveQueue = [];
|
||||||
|
|
||||||
|
Res.Error = false;
|
||||||
|
Res.Content = [];
|
||||||
|
for (video of VideoArray)
|
||||||
|
{
|
||||||
|
video = video.trim()
|
||||||
|
if (YoutubeRegex.exec(video))
|
||||||
|
{
|
||||||
|
let VideoID = video.match(YoutubeRegex)[5];
|
||||||
|
// generate ID lol
|
||||||
|
ResolveQueue.push(VideoID);
|
||||||
|
Res.Content.push({
|
||||||
|
id: VideoID,
|
||||||
|
url: video,
|
||||||
|
valid: true,
|
||||||
|
action: 'Resolving'
|
||||||
|
});
|
||||||
|
} else
|
||||||
|
{
|
||||||
|
Res.Content.push({
|
||||||
|
id: -1, // -1 means not resolved
|
||||||
|
url: video, // used as ID on the client
|
||||||
|
valid: false,
|
||||||
|
action: 'Error'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
socket.emit('VideoListResolution', Res);
|
||||||
|
|
||||||
|
if (ResolveQueue.length == 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
const Resolution = await YoutubeHelper.GetVideoInfoArr(ResolveQueue);
|
||||||
|
|
||||||
|
const ResolutionRes = {
|
||||||
|
Error: false,
|
||||||
|
Content: Resolution
|
||||||
|
};
|
||||||
|
|
||||||
|
socket.emit('VideoListResolved', ResolutionRes);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
function ErorrsBuilder(...errors)
|
||||||
|
{
|
||||||
|
// errors format
|
||||||
|
// Errors: [{
|
||||||
|
// type: string,
|
||||||
|
// content: string
|
||||||
|
// }],
|
||||||
|
let ret = [];
|
||||||
|
let j = 0;
|
||||||
|
for (let i = 0; i < errors.length; i += 2)
|
||||||
|
{
|
||||||
|
ret[j] = {
|
||||||
|
type: errors[i],
|
||||||
|
content: errors[i+1]
|
||||||
|
};
|
||||||
|
j++;
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports.App = app;
|
||||||
|
module.exports.Http = http;
|
||||||
|
module.exports.Io = io;
|
||||||
|
|
||||||
|
|||||||
3
src/youtubedownloader.js
Normal file
3
src/youtubedownloader.js
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
const YTDL = require('ytdl-core');
|
||||||
|
|
||||||
|
|
||||||
@@ -1,124 +1,128 @@
|
|||||||
const logger = require('./logger');
|
const Config = require('./config');
|
||||||
|
const Logger = require('./logger');
|
||||||
const fs = require('fs');
|
|
||||||
const ytdl = require('ytdl-core');
|
const YTDL = require('ytdl-core');
|
||||||
|
|
||||||
module.exports.resolveVideos = async (arr) => {
|
// TODO: does the video resolver need a queue?
|
||||||
let output = {contents: false, data: {}};
|
|
||||||
|
// cache
|
||||||
for (let video of arr) {
|
let ResolutionCache = [];
|
||||||
if (video == '' || video == ' ') continue;
|
|
||||||
try {
|
function HitCache(id)
|
||||||
if (await ytdl.validateURL(video)) {
|
{
|
||||||
const info = await ytdl.getBasicInfo(video);
|
if (ResolutionCache[id])
|
||||||
if (!info) {
|
{
|
||||||
output.data[video] = {found: false};
|
Logger.Log(`Cache hit! ${id}`)
|
||||||
return;
|
ResolutionCache[id].LastUsed = Date.now();
|
||||||
}
|
return ResolutionCache[id].Video;
|
||||||
output.data[video] = {
|
} else
|
||||||
found: true,
|
{
|
||||||
error: false,
|
return false;
|
||||||
title: info.title,
|
}
|
||||||
thumbnail: info.thumbnail_url.replace('default', 'maxresdefault'),
|
}
|
||||||
runtime: info.length_seconds
|
|
||||||
}
|
function RegisterCache(video)
|
||||||
output.contents = true;
|
{
|
||||||
} else {
|
ResolutionCache[video.player_response.videoDetails.videoId] = {
|
||||||
output.data[video] = {found: false, error: true};
|
Id: video.player_response.videoDetails.videoId,
|
||||||
output.contents = true;
|
Video: video,
|
||||||
}
|
LastUsed: Date.now(),
|
||||||
} catch (e) {
|
TimeCreated: Date.now()
|
||||||
if (!e.toString().includes('Error: This video contains content')) {
|
}
|
||||||
output.data[video] = {found: false};
|
}
|
||||||
output.contents = true;
|
|
||||||
logger.log(`Error resolving video ${video}: ${e}`);
|
function CleanCache()
|
||||||
} else {
|
{
|
||||||
output.data[video] = {
|
// TODO: fix weird bug where a cleaned
|
||||||
found: true,
|
// entry's timeused lingers
|
||||||
error: true,
|
let ExpiredEntrys = [];
|
||||||
title: 'Not found: This video is blocked in your country',
|
for (id in ResolutionCache)
|
||||||
thumbnail: 'ERRORED',
|
{
|
||||||
runtime: 'NONE'
|
entry = ResolutionCache[id];
|
||||||
}
|
// if cache entry has expired
|
||||||
}
|
const LastUsed = Date.now() - entry.LastUsed;
|
||||||
}
|
if (LastUsed > Config.Configuration.CacheTimeToUse)
|
||||||
}
|
{
|
||||||
|
// remove expired entry
|
||||||
return output;
|
Logger.Log(`Cache entry '${id}' expired`);
|
||||||
}
|
ExpiredEntrys.push(id);
|
||||||
|
delete entry;
|
||||||
let downloadQueue = [];
|
}
|
||||||
|
}
|
||||||
module.exports.setupDownloadQueue = async (arr, socket, options) => {
|
|
||||||
let numOfDownloads;
|
for (expiredEntry of ExpiredEntrys)
|
||||||
|
{
|
||||||
for (const [key, value] of Object.entries(arr))
|
delete ResolutionCache[expiredEntry];
|
||||||
if (value.error) delete arr[key];
|
}
|
||||||
|
}
|
||||||
downloadQueue[socket.id] = {
|
|
||||||
count: 0,
|
module.exports.InitResolverCache = async () =>
|
||||||
videos: { }
|
{
|
||||||
}
|
setInterval(CleanCache, Config.Configuration.CacheCleanInterval);
|
||||||
|
Logger.Log('Video resolver cache settup')
|
||||||
for (const [key, value] of Object.entries(arr))
|
}
|
||||||
if (ytdl.validateURL(key))
|
|
||||||
socket.emit('download-progress', {video: key, percent: "Queued", title: value.title});
|
module.exports.GetVideoInfoArr = async (arr) =>
|
||||||
|
{
|
||||||
await new Promise(async (resolve, reject) => {
|
let ret = [];
|
||||||
for (const [key, value] of Object.entries(arr)) {
|
// TODO: make async AND retain order
|
||||||
if (ytdl.validateURL(key)) {
|
for (video of arr)
|
||||||
await runQueueAsync(socket.id);
|
{
|
||||||
await download(key, value.title, socket, options.audioOnly);
|
ret.push(await this.GetVideoInfo(video));
|
||||||
}
|
}
|
||||||
// if (arr.indexOf(key) == arr.length) {resolve();}
|
return ret;
|
||||||
}
|
}
|
||||||
resolve();
|
|
||||||
});
|
module.exports.GetVideoInfo = async (video) =>
|
||||||
socket.emit('queue-concluded');
|
{
|
||||||
}
|
try
|
||||||
|
{
|
||||||
async function runQueueAsync(socketID) {
|
const CacheHit = HitCache(video);
|
||||||
// Ready for proper queueuing
|
|
||||||
}
|
let Video = {};
|
||||||
|
|
||||||
async function download(video, videoName ,socket, audioOnly, path = './') {
|
if (CacheHit)
|
||||||
return new Promise(async (resolve, reject) => {
|
{
|
||||||
let stream;
|
Video = CacheHit
|
||||||
try {
|
} else
|
||||||
if (audioOnly) {
|
{
|
||||||
stream = await ytdl(video, {quality: 'highest', filter: (format) => format.container === 'mp4'});
|
// TODO: is the YouTube API faster for this?
|
||||||
stream.pipe(fs.createWriteStream(`${path}/${(videoName).replace(/\//, '-')}.mp4`));
|
Logger.Log(`Resolving '${video}'`);
|
||||||
} else {
|
Video = await YTDL.getInfo(video);
|
||||||
stream = await ytdl(video, {quality: 'highest', filter: "audioonly"});
|
// register the info into the cache
|
||||||
stream.pipe(fs.createWriteStream(`${path}/${(videoName).replace(/\//, '-')}.mp3`));
|
RegisterCache(Video);
|
||||||
}
|
}
|
||||||
|
|
||||||
stream.on('response', (res) => {
|
|
||||||
let totalSize = res.headers['content-length'];
|
let Res = BuildBasicInfoFromInfo(Video);
|
||||||
let dataRead = 0;
|
return Res;
|
||||||
let lastPercent = 0;
|
|
||||||
res.on('data', (data) => {
|
} catch (e)
|
||||||
dataRead += data.length;
|
{
|
||||||
let percent = Math.floor((dataRead / totalSize) * 100) + '%';
|
Logger.Log(`Error resolving video '${video}', ${e}`);
|
||||||
if (percent != lastPercent) {
|
return {
|
||||||
socket.emit('download-progress', {video: video, percent: percent, title: videoName});
|
id: video,
|
||||||
}
|
Error: true,
|
||||||
lastPercent = percent;
|
Errors: [ "Video cannot resolve" ]
|
||||||
});
|
};
|
||||||
|
}
|
||||||
res.on('end', () => {
|
}
|
||||||
logger.log(`Socket id '${socket.id}' finished downloading ${videoName}`)
|
|
||||||
socket.emit('download-done', {video: video, title: videoName});
|
function BuildBasicInfoFromInfo(info)
|
||||||
resolve('complete');
|
{
|
||||||
});
|
let ret = {};
|
||||||
|
ret.id = info.player_response.videoDetails.videoId;
|
||||||
logger.log(`Socket id '${socket.id}' is downloading ${videoName}`);
|
ret.title = info.player_response.videoDetails.title;
|
||||||
});
|
ret.desc = info.player_response.videoDetails.shortDescription;
|
||||||
} catch (e) {
|
ret.views = info.player_response.videoDetails.viewCount;
|
||||||
logger.log(`Socket id '${socket.id}' failed to download ${videoName}: ${e}`);
|
if (!info.player_response.videoDetails.thumbnail.thumbnails[0])
|
||||||
socket.emit('download-done', {video: video, title: videoName});
|
{
|
||||||
socket.emit('download-progress', {video: video, percent: "Failed", title: videoName});
|
ret.thumbnail = info.player_response.videoDetails.thumbnail.thumbnails[1].url;
|
||||||
resolve('error');
|
} else
|
||||||
}
|
{
|
||||||
});
|
ret.thumbnail = info.player_response.videoDetails.thumbnail.thumbnails[0].url;
|
||||||
}
|
}
|
||||||
|
ret.channel = info.player_response.videoDetails.author;
|
||||||
|
ret.runtime = info.player_response.videoDetails.lengthSeconds;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user