New downloader version
This commit is contained in:
commit
ac3f6e5f3b
20 changed files with 2977 additions and 0 deletions
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/node_modules/
|
||||
/proxy/proxy.json
|
8
.idea/.gitignore
vendored
Normal file
8
.idea/.gitignore
vendored
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
6
.idea/discord.xml
Normal file
6
.idea/discord.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="DiscordProjectSettings">
|
||||
<option name="show" value="PROJECT_FILES" />
|
||||
</component>
|
||||
</project>
|
6
.idea/jsLibraryMappings.xml
Normal file
6
.idea/jsLibraryMappings.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="JavaScriptLibraryMappings">
|
||||
<includedPredefinedLibrary name="Node.js Core" />
|
||||
</component>
|
||||
</project>
|
6
.idea/misc.xml
Normal file
6
.idea/misc.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager">
|
||||
<output url="file://$PROJECT_DIR$/out" />
|
||||
</component>
|
||||
</project>
|
8
.idea/modules.xml
Normal file
8
.idea/modules.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/jeff-downloader2.iml" filepath="$PROJECT_DIR$/jeff-downloader2.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
11
.idea/runConfigurations/bin_www.xml
Normal file
11
.idea/runConfigurations/bin_www.xml
Normal file
|
@ -0,0 +1,11 @@
|
|||
<component name="ProjectRunConfigurationManager">
|
||||
<configuration default="false" name="bin/www" type="NodeJSConfigurationType" path-to-js-file="bin/www" working-dir="$PROJECT_DIR$">
|
||||
<envs>
|
||||
<env name="NODE_ENV" value="production DEBUG=jeff-downloader2:*" />
|
||||
</envs>
|
||||
<EXTENSION ID="com.jetbrains.nodejs.run.NodeJSStartBrowserRunConfigurationExtension">
|
||||
<browser url="http://localhost:3000/" />
|
||||
</EXTENSION>
|
||||
<method v="2" />
|
||||
</configuration>
|
||||
</component>
|
6
.idea/vcs.xml
Normal file
6
.idea/vcs.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
44
app.js
Normal file
44
app.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
var createError = require('http-errors');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var cookieParser = require('cookie-parser');
|
||||
var logger = require('morgan');
|
||||
const bodyParser = require('body-parser');
|
||||
|
||||
|
||||
var indexRouter = require('./routes/index');
|
||||
var usersRouter = require('./routes/users');
|
||||
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'ejs');
|
||||
|
||||
app.use(logger('dev'));
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: false }));
|
||||
app.use(cookieParser());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
app.use('/', indexRouter);
|
||||
app.use('/users', usersRouter);
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
next(createError(404));
|
||||
});
|
||||
|
||||
// error handler
|
||||
app.use(function(err, req, res, next) {
|
||||
// set locals, only providing error in development
|
||||
res.locals.message = err.message;
|
||||
res.locals.error = req.app.get('env') === 'development' ? err : {};
|
||||
|
||||
// render the error page
|
||||
res.status(err.status || 500);
|
||||
res.render('error');
|
||||
});
|
||||
|
||||
module.exports = app;
|
90
bin/www
Executable file
90
bin/www
Executable file
|
@ -0,0 +1,90 @@
|
|||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* Module dependencies.
|
||||
*/
|
||||
|
||||
var app = require('../app');
|
||||
var debug = require('debug')('jeff-downloader2:server');
|
||||
var http = require('http');
|
||||
|
||||
/**
|
||||
* Get port from environment and store in Express.
|
||||
*/
|
||||
|
||||
var port = normalizePort(process.env.PORT || '3000');
|
||||
app.set('port', port);
|
||||
|
||||
/**
|
||||
* Create HTTP server.
|
||||
*/
|
||||
|
||||
var server = http.createServer(app);
|
||||
|
||||
/**
|
||||
* Listen on provided port, on all network interfaces.
|
||||
*/
|
||||
|
||||
server.listen(port);
|
||||
server.on('error', onError);
|
||||
server.on('listening', onListening);
|
||||
|
||||
/**
|
||||
* Normalize a port into a number, string, or false.
|
||||
*/
|
||||
|
||||
function normalizePort(val) {
|
||||
var port = parseInt(val, 10);
|
||||
|
||||
if (isNaN(port)) {
|
||||
// named pipe
|
||||
return val;
|
||||
}
|
||||
|
||||
if (port >= 0) {
|
||||
// port number
|
||||
return port;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "error" event.
|
||||
*/
|
||||
|
||||
function onError(error) {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
var bind = typeof port === 'string'
|
||||
? 'Pipe ' + port
|
||||
: 'Port ' + port;
|
||||
|
||||
// handle specific listen errors with friendly messages
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error(bind + ' requires elevated privileges');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error(bind + ' is already in use');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Event listener for HTTP server "listening" event.
|
||||
*/
|
||||
|
||||
function onListening() {
|
||||
var addr = server.address();
|
||||
var bind = typeof addr === 'string'
|
||||
? 'pipe ' + addr
|
||||
: 'port ' + addr.port;
|
||||
debug('Listening on ' + bind);
|
||||
}
|
8
jeff-downloader2.iml
Normal file
8
jeff-downloader2.iml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="WEB_MODULE" version="4">
|
||||
<component name="NewModuleRootManager" inherit-compiler-output="true">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
1876
package-lock.json
generated
Normal file
1876
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load diff
22
package.json
Normal file
22
package.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "jeff-downloader2",
|
||||
"version": "0.0.1",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "node ./bin/www"
|
||||
},
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"cookie-parser": "~1.4.4",
|
||||
"debug": "~2.6.9",
|
||||
"ejs": "~2.6.1",
|
||||
"express": "~4.16.1",
|
||||
"express-formidable": "^1.2.0",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"formidable": "^1.2.2",
|
||||
"http-errors": "~1.6.3",
|
||||
"morgan": "~1.9.1",
|
||||
"youtube-dl": "^3.5.0",
|
||||
"youtube-dl-exec": "^1.2.3"
|
||||
}
|
||||
}
|
149
public/stylesheets/background.css
Normal file
149
public/stylesheets/background.css
Normal file
|
@ -0,0 +1,149 @@
|
|||
/* https://codepen.io/mohaiman/pen/MQqMyo */
|
||||
|
||||
*{
|
||||
margin: 0px;
|
||||
padding: 0px;
|
||||
}
|
||||
|
||||
body{
|
||||
font-family: 'Exo', sans-serif;
|
||||
}
|
||||
|
||||
|
||||
.context {
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
top:50vh;
|
||||
|
||||
}
|
||||
|
||||
.context h1{
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
font-size: 50px;
|
||||
}
|
||||
|
||||
|
||||
.area{
|
||||
background: rgb(59,44,207);
|
||||
background: -webkit-linear-gradient(90deg, rgba(59,44,207,1) 0%, rgba(140,41,151,1) 100%);
|
||||
width: 100%;
|
||||
height:100vh;
|
||||
|
||||
|
||||
}
|
||||
|
||||
.circles{
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.circles li{
|
||||
position: absolute;
|
||||
display: block;
|
||||
list-style: none;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
animation: animate 25s linear infinite;
|
||||
bottom: -150px;
|
||||
|
||||
}
|
||||
|
||||
.circles li:nth-child(1){
|
||||
left: 25%;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
|
||||
.circles li:nth-child(2){
|
||||
left: 10%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
animation-delay: 2s;
|
||||
animation-duration: 12s;
|
||||
}
|
||||
|
||||
.circles li:nth-child(3){
|
||||
left: 70%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
.circles li:nth-child(4){
|
||||
left: 40%;
|
||||
width: 60px;
|
||||
height: 60px;
|
||||
animation-delay: 0s;
|
||||
animation-duration: 18s;
|
||||
}
|
||||
|
||||
.circles li:nth-child(5){
|
||||
left: 65%;
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
animation-delay: 0s;
|
||||
}
|
||||
|
||||
.circles li:nth-child(6){
|
||||
left: 75%;
|
||||
width: 110px;
|
||||
height: 110px;
|
||||
animation-delay: 3s;
|
||||
}
|
||||
|
||||
.circles li:nth-child(7){
|
||||
left: 35%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
animation-delay: 7s;
|
||||
}
|
||||
|
||||
.circles li:nth-child(8){
|
||||
left: 50%;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
animation-delay: 15s;
|
||||
animation-duration: 45s;
|
||||
}
|
||||
|
||||
.circles li:nth-child(9){
|
||||
left: 20%;
|
||||
width: 15px;
|
||||
height: 15px;
|
||||
animation-delay: 2s;
|
||||
animation-duration: 35s;
|
||||
}
|
||||
|
||||
.circles li:nth-child(10){
|
||||
left: 85%;
|
||||
width: 150px;
|
||||
height: 150px;
|
||||
animation-delay: 0s;
|
||||
animation-duration: 11s;
|
||||
}
|
||||
|
||||
|
||||
|
||||
@keyframes animate {
|
||||
|
||||
0%{
|
||||
transform: translateY(0) rotate(0deg);
|
||||
opacity: 1;
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
100%{
|
||||
transform: translateY(-1000px) rotate(720deg);
|
||||
opacity: 0;
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
}
|
109
public/stylesheets/index.css
Normal file
109
public/stylesheets/index.css
Normal file
|
@ -0,0 +1,109 @@
|
|||
.downloadbtn {
|
||||
animation: colorchange 13s infinite;
|
||||
animation-direction: alternate-reverse;
|
||||
}
|
||||
|
||||
a:link {
|
||||
color: #A0FF9A;
|
||||
}
|
||||
|
||||
a:visited {
|
||||
color: #A7FF56;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: #DCFFA5 !important;
|
||||
}
|
||||
|
||||
.checkbox:hover, .radio:hover {
|
||||
color: lightgray !important;
|
||||
}
|
||||
|
||||
.gradientBG {
|
||||
background: rgb(59,44,207);
|
||||
background: linear-gradient(90deg, rgba(59,44,207,1) 0%, rgba(140,41,151,1) 100%);
|
||||
}
|
||||
|
||||
/* Grow */
|
||||
.hvr-grow {
|
||||
display: inline-block;
|
||||
vertical-align: middle;
|
||||
transform: translateZ(0);
|
||||
box-shadow: 0 0 1px rgba(0, 0, 0, 0);
|
||||
backface-visibility: hidden;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
transition-duration: 0.3s;
|
||||
transition-property: transform;
|
||||
}
|
||||
|
||||
.hvr-grow:hover,
|
||||
.hvr-grow:focus,
|
||||
.hvr-grow:active {
|
||||
transform: scale(1.1);
|
||||
}
|
||||
|
||||
.fadein {
|
||||
animation: fadein 2s;
|
||||
}
|
||||
|
||||
.fadeout {
|
||||
animation: fadeout 2s;
|
||||
}
|
||||
|
||||
@keyframes fadein {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
@keyframes fadeout {
|
||||
from { opacity: 1; }
|
||||
to { opacity: 0; }
|
||||
}
|
||||
|
||||
@keyframes colorchange {
|
||||
0% {
|
||||
background-color: #ff2400;
|
||||
color: white;
|
||||
}
|
||||
10% {
|
||||
background-color: #e81d1d;
|
||||
}
|
||||
20% {
|
||||
background-color: #e8b71d;
|
||||
color: black;
|
||||
}
|
||||
30% {
|
||||
background-color: #e3e81d;
|
||||
}
|
||||
40% {
|
||||
background-color: #1de840;
|
||||
color: white;
|
||||
}
|
||||
50% {
|
||||
background-color: #1ddde8;
|
||||
color: black;
|
||||
}
|
||||
60% {
|
||||
background-color: #2b1de8;
|
||||
color: white;
|
||||
}
|
||||
70% {
|
||||
background-color: #dd00f3;
|
||||
}
|
||||
80% {
|
||||
background-color: #dd00f3;
|
||||
}
|
||||
90% {
|
||||
background-color: #ff2400;
|
||||
}
|
||||
100% {
|
||||
background-color: #ff2400;
|
||||
}
|
||||
}
|
||||
|
||||
progress::-webkit-progress-value {
|
||||
-webkit-transition : width 1s ease;
|
||||
-moz-transition : width 1s ease;
|
||||
-o-transition : width 1s ease;
|
||||
transition : width 1s ease;
|
||||
}
|
8
public/stylesheets/style.css
Normal file
8
public/stylesheets/style.css
Normal file
|
@ -0,0 +1,8 @@
|
|||
body {
|
||||
padding: 50px;
|
||||
font: 14px "Lucida Grande", Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #00B7FF;
|
||||
}
|
336
routes/index.js
Normal file
336
routes/index.js
Normal file
|
@ -0,0 +1,336 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
const youtubedl = require('youtube-dl');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const ffmpeg = require('fluent-ffmpeg');
|
||||
const formidable = require('formidable');
|
||||
const { version } = require('../package.json');
|
||||
const proxy = require('../proxy/proxy.json');
|
||||
|
||||
let progress = {};
|
||||
let viewCounter = 0;
|
||||
let files = [];
|
||||
let day;
|
||||
let month;
|
||||
let announcementArray = [];
|
||||
let announcement;
|
||||
|
||||
function formatBytes(bytes, decimals = 2) { // https://stackoverflow.com/a/18650828
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
|
||||
const k = 1024;
|
||||
const dm = decimals < 0 ? 0 : decimals;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
|
||||
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(dm)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
/* GET home page. */
|
||||
router.get('/', function(req, res, next) {
|
||||
viewCounter++;
|
||||
|
||||
files = [];
|
||||
let file = [];
|
||||
for (let f of fs.readdirSync('./public/uploads')) {
|
||||
if (f.endsWith('.mp4') || f.endsWith('.webm') || f.endsWith('.mp3') || f.endsWith('.flac'))
|
||||
file.push(f)
|
||||
}
|
||||
// get the 5 most recent files
|
||||
file = file.sort((a, b) => {
|
||||
if ((a || b).endsWith('.mp4') || (a || b).endsWith('.webm') || (a || b).endsWith('.mp3') || (a || b).endsWith('.flac')) {
|
||||
let time1 = fs.statSync(`./public/uploads/${b}`).ctime;
|
||||
let time2 = fs.statSync(`./public/uploads/${a}`).ctime;
|
||||
if (time1 < time2) return -1;
|
||||
if (time1 > time2) return 1;
|
||||
}
|
||||
return 0;
|
||||
}).slice(0, 5)
|
||||
|
||||
// Save space by deleting file that doesn't appear in the recent feed
|
||||
for (let f of fs.readdirSync('./public/uploads')) {
|
||||
if (!file.includes(f) && (f != 'hidden' && f != '.keep')) {
|
||||
if (fs.existsSync(`./public/uploads/${f}`))
|
||||
fs.unlinkSync(`./public/uploads/${f}`);
|
||||
|
||||
if (fs.existsSync(`./public/thumbnail/${f}`))
|
||||
fs.unlinkSync(`./public/thumbnail/${f}`);
|
||||
|
||||
if (fs.existsSync(`./public/thumbnail/${f}.png`))
|
||||
fs.unlinkSync(`./public/thumbnail/${f}.png`);
|
||||
}
|
||||
}
|
||||
|
||||
for (let f of file) {
|
||||
let fileInfo = formatBytes(fs.statSync(`./public/uploads/${f}`).size).split(' ');
|
||||
let defaultFiles = { name: f.replace(path.extname(f), ''), size: fileInfo[0], unit: fileInfo[1], date: fs.statSync(`./public/uploads/${f}`).ctime, location: `uploads/${f}`, ext: path.extname(f), thumbnail: `/thumbnail/${f}`, img: `/thumbnail/${f.replace(path.extname(f), '.png')}` };
|
||||
|
||||
if (f.endsWith('.mp3') || f.endsWith('.flac')) {
|
||||
defaultFiles.thumbnail = `./thumbnail/${f.replace(path.extname(f), '.png')}`
|
||||
}
|
||||
files.push(defaultFiles);
|
||||
}
|
||||
|
||||
res.render('index', { version: version, files: files, viewCounter: viewCounter, proxy: proxy });
|
||||
});
|
||||
|
||||
router.get('/status/:uuid', function (req, res ,next) {
|
||||
let uuid = req.params.uuid;
|
||||
if (progress[uuid]) {
|
||||
res.send(progress[uuid]);
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/', async function(req, res, next) {
|
||||
let data;
|
||||
const form = formidable({ multiples: true});
|
||||
|
||||
data = await new Promise(function(resolve, reject) {
|
||||
form.parse(req, function(err, fields, files) {
|
||||
if (err) {
|
||||
reject(err)
|
||||
}
|
||||
resolve({...fields})
|
||||
})
|
||||
});
|
||||
|
||||
|
||||
console.log(data.url);
|
||||
if (data.url === undefined) {
|
||||
//res.render('index', { error: true, errormsg: 'You didn\'t input a link'})
|
||||
return res.send('You didn\'t input a link')
|
||||
}
|
||||
|
||||
let quality;
|
||||
if (data.quality === 'worst') {
|
||||
quality = 'worst';
|
||||
} else {
|
||||
quality = 'best';
|
||||
}
|
||||
|
||||
if (data.format === undefined) {
|
||||
data.format = 'mp4';
|
||||
}
|
||||
|
||||
if (data.url.toLowerCase().includes('porn')) {
|
||||
data.feed = 'on';
|
||||
}
|
||||
|
||||
let videoID;
|
||||
if (data.sponsorBlock) {
|
||||
let regExp = /^.*((youtu.be\/)|(v\/)|(\/u\/\w\/)|(embed\/)|(watch\?))\??v?=?([^#&?]*).*/;
|
||||
let match = data.url.match(regExp);
|
||||
videoID = (match&&match[7].length==11)? match[7] : false;
|
||||
if (!videoID) {
|
||||
return res.json({error: 'To use sponsorBlock you need a valid youtube link!'});
|
||||
//res.render('index', { error: true, errormsg: 'To use sponsorBlock you need a valid youtube link!'})
|
||||
}
|
||||
}
|
||||
|
||||
let options = ['--format=mp4', '-f', quality];
|
||||
if (data.proxy !== 'none') {
|
||||
options.push('--proxy');
|
||||
options.push(data.proxy);
|
||||
}
|
||||
|
||||
console.log(options);
|
||||
|
||||
let video = youtubedl(data.url, options);
|
||||
|
||||
video.on('error', function(err) {
|
||||
console.error(err);
|
||||
res.json({ error: err.stderr});
|
||||
});
|
||||
|
||||
let ext;
|
||||
let size = 0;
|
||||
video.on('info', function(info) {
|
||||
size = info.size;
|
||||
if (size / 1000000.0 > 10000) return res.json({error: 'Sorry, but I don\'t have enough storage to store something this big.'});
|
||||
// Set file name
|
||||
ext = info.ext;
|
||||
let title = info.title.slice(0,50);
|
||||
DLFile = `${title.replace(/\s/g, '')}.${ext}`;
|
||||
DLFile = DLFile.replace(/[()]|[/]|[\\]|[!]|[?]/g, '');
|
||||
DLFile = DLFile.replace(',', '');
|
||||
|
||||
// If no title use the ID
|
||||
if (title === '_') title = `_${info.id}`;
|
||||
// If user want to hide from the feed
|
||||
if (data.feed === 'on')
|
||||
DLFile = `hidden/${title}.${ext}`;
|
||||
|
||||
if (data.sponsorBlock) video.pipe(fs.createWriteStream(`./public/uploads/hidden/${DLFile}`));
|
||||
else video.pipe(fs.createWriteStream(`./public/uploads/${DLFile}`));
|
||||
});
|
||||
|
||||
let pos = 0
|
||||
video.on('data', (chunk) => {
|
||||
pos += chunk.length
|
||||
// `size` should not be 0 here.
|
||||
if (size) {
|
||||
let percent = (pos / size * 100).toFixed(2)
|
||||
progress[data.uuid] = percent;
|
||||
}
|
||||
})
|
||||
|
||||
video.on('end', function() {
|
||||
progress[data.uuid] = 0;
|
||||
if (data.format === 'mp4' || data.format === 'webm') {
|
||||
if (data.sponsorBlock) { // WARNING: THIS PART SUCK
|
||||
let filter = '';
|
||||
let abc = ['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z'];
|
||||
fetch(`https://sponsor.ajay.app/api/skipSegments?videoID=${videoID}`)
|
||||
.then(res => {
|
||||
if (res.status === 404) {
|
||||
return res.json({error: 'Couldn\'t find any SponsorBlock data for this video.'});
|
||||
//return this.res.render('index', { error: true, errormsg: 'Couldn\'t find any SponsorBlock data for this video.'})
|
||||
}
|
||||
return res.json();
|
||||
})
|
||||
.then(json => {
|
||||
if (json === undefined) return;
|
||||
let i = 0;
|
||||
let previousEnd;
|
||||
let usedLetter = [];
|
||||
json.forEach(sponsor => {
|
||||
usedLetter.push(abc[i]);
|
||||
if (i === 0) {
|
||||
filter += `[0:v]trim=start=0:end=${sponsor.segment[0]},setpts=PTS-STARTPTS[${abc[i]}v];`;
|
||||
filter += `[0:a]atrim=start=0:end=${sponsor.segment[0]},asetpts=PTS-STARTPTS[${abc[i]}a];`;
|
||||
} else {
|
||||
filter += `[0:v]trim=start=${previousEnd}:end=${sponsor.segment[0]},setpts=PTS-STARTPTS[${abc[i]}v];`;
|
||||
filter += `[0:a]atrim=start=${previousEnd}:end=${sponsor.segment[0]},asetpts=PTS-STARTPTS[${abc[i]}a];`;
|
||||
}
|
||||
previousEnd = sponsor.segment[1];
|
||||
i++;
|
||||
});
|
||||
usedLetter.push(abc[i]);
|
||||
filter += `[0:v]trim=start=${previousEnd},setpts=PTS-STARTPTS[${abc[i]}v];`;
|
||||
filter += `[0:a]atrim=start=${previousEnd},asetpts=PTS-STARTPTS[${abc[i]}a];`;
|
||||
let video = '';
|
||||
let audio = '';
|
||||
usedLetter.forEach(letter => {
|
||||
video += `[${letter}v]`
|
||||
audio += `[${letter}a]`
|
||||
});
|
||||
filter += `${video}concat=n=${i + 1}[outv];`;
|
||||
filter += `${audio}concat=n=${i + 1}:v=0:a=1[outa]`;
|
||||
|
||||
ffmpeg(`./public/uploads/hidden/${DLFile}`)
|
||||
.inputFormat('mp4')
|
||||
.complexFilter(filter)
|
||||
.outputOptions('-map [outv]')
|
||||
.outputOptions('-map [outa]')
|
||||
.save(`./public/uploads/${DLFile}`)
|
||||
.on('error', function(err, stdout, stderr) {
|
||||
console.log('Cannot process video: ' + err.message);
|
||||
return res.json({error: err.message});
|
||||
//return res.render('index', { error: true, errormsg: err.message})
|
||||
})
|
||||
.on('end', () => {
|
||||
console.log('end');
|
||||
//res.attachment(`./public/uploads/${DLFile}`)
|
||||
res.json({url: `uploads/${DLFile}`})
|
||||
if (data.feed !== 'on') generateThumbnail(DLFile);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// If user requested mp4 directly attach the file
|
||||
res.json({url: `uploads/${DLFile}`})
|
||||
if (data.feed !== 'on') generateThumbnail(DLFile);
|
||||
}
|
||||
} else {
|
||||
// If user requested an audio format, convert it
|
||||
ffmpeg(`./public/uploads/${DLFile}`)
|
||||
.noVideo()
|
||||
.audioChannels('2')
|
||||
.audioFrequency('44100')
|
||||
.audioBitrate('320k')
|
||||
.format(data.format)
|
||||
.save(`./public/uploads/${DLFile.replace(`.${ext}`, `.${data.format}`)}`)
|
||||
.on('error', function(err, stdout, stderr) {
|
||||
console.log('Cannot process video: ' + err.message);
|
||||
return res.json({error: err.message});
|
||||
//return res.render('index', { error: true, errormsg: err.message})
|
||||
})
|
||||
.on('end', () => {
|
||||
fs.unlinkSync(`./public/uploads/${DLFile}`);
|
||||
if (data.feed !== 'on') generateWaveform(DLFile.replace(`.${ext}`, `.${data.format}`));
|
||||
return res.json({url: `uploads/${DLFile.replace(`.${ext}`, `.${data.format}`)}`});
|
||||
//return res.attachment(`./public/uploads/${DLFile.replace(`.${ext}`, `.${data.format}`)}`);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
|
||||
async function generateWaveform(f) {
|
||||
ffmpeg(`./public/uploads/${f}`)
|
||||
.complexFilter('[0:a]aformat=channel_layouts=mono,compand=gain=-6,showwavespic=s=600x120:colors=#9cf42f[fg];color=s=600x120:color=#44582c,drawgrid=width=iw/10:height=ih/5:color=#9cf42f@0.1[bg];[bg][fg]overlay=format=rgb,drawbox=x=(iw-w)/2:y=(ih-h)/2:w=iw:h=1:color=#9cf42f')
|
||||
.frames(1)
|
||||
.noVideo()
|
||||
.noAudio()
|
||||
.duration(0.1)
|
||||
.on('error', function(err, stdout, stderr) {
|
||||
return console.log('Cannot process video: ' + err.message);
|
||||
})
|
||||
.on('end', () => {
|
||||
generateThumbnail(`../thumbnail/${f.replace(path.extname(f), '.mp4')}`);
|
||||
})
|
||||
.save(`./public/thumbnail/${f.replace(path.extname(f), '.mp4')}`);
|
||||
}
|
||||
|
||||
async function generateThumbnail(f) {
|
||||
ffmpeg(`./public/uploads/${f}`)
|
||||
.screenshots({
|
||||
timestamps: ['20%'],
|
||||
size: '720x480',
|
||||
folder: './public/thumbnail/',
|
||||
filename: f.replace(path.extname(f), '.png')
|
||||
})
|
||||
.on('error', function(err, stdout, stderr) {
|
||||
return console.log('Cannot process video: ' + err.message);
|
||||
});
|
||||
|
||||
if (!fs.existsSync(`./public/thumbnail/tmp/${f}`) && !f.startsWith('../thumbnail'))
|
||||
fs.mkdirSync(`./public/thumbnail/tmp/${f}`)
|
||||
|
||||
ffmpeg(`./public/uploads/${f}`)
|
||||
.complexFilter('select=gt(scene\\,0.8)')
|
||||
.frames(10)
|
||||
.complexFilter('fps=fps=1/10')
|
||||
.save(`./public/thumbnail/tmp/${f}/%03d.png`)
|
||||
.on('error', function(err, stdout, stderr) {
|
||||
return console.log('Cannot process video: ' + err.message);
|
||||
})
|
||||
.on('end', () => {
|
||||
ffmpeg(`./public/thumbnail/tmp/${f}/%03d.png`)
|
||||
.complexFilter('zoompan=d=(.5+.5)/.5:s=640x480:fps=1/.5,framerate=25:interp_start=0:interp_end=255:scene=100')
|
||||
.format('mp4')
|
||||
.save(`./public/thumbnail/${f}`)
|
||||
.on('error', function(err, stdout, stderr) {
|
||||
return console.log('Cannot process video: ' + err.message);
|
||||
})
|
||||
.on('end', () => {
|
||||
// Save space by deleting tmp directory
|
||||
for (let files of fs.readdirSync(`./public/thumbnail/tmp/${f}`)) {
|
||||
if (files == '.keep') return;
|
||||
fs.unlinkSync(`./public/thumbnail/tmp/${f}/${files}`);
|
||||
}
|
||||
fs.rmdirSync(`./public/thumbnail/tmp/${f}`);
|
||||
});
|
||||
});
|
||||
}
|
9
routes/users.js
Normal file
9
routes/users.js
Normal file
|
@ -0,0 +1,9 @@
|
|||
var express = require('express');
|
||||
var router = express.Router();
|
||||
|
||||
/* GET users listing. */
|
||||
router.get('/', function(req, res, next) {
|
||||
res.send('respond with a resource');
|
||||
});
|
||||
|
||||
module.exports = router;
|
3
views/error.ejs
Normal file
3
views/error.ejs
Normal file
|
@ -0,0 +1,3 @@
|
|||
<h1><%= message %></h1>
|
||||
<h2><%= error.status %></h2>
|
||||
<pre><%= error.stack %></pre>
|
270
views/index.ejs
Normal file
270
views/index.ejs
Normal file
|
@ -0,0 +1,270 @@
|
|||
<!--
|
||||
What are you doing here 😳😳😳😳
|
||||
I guess have fun looking at the html, no easter egg to find here.
|
||||
Come take a look here https://git.namejeff.xyz/Supositware/jeff-downloader for all my bad coding
|
||||
-->
|
||||
<!DOCTYPE html>
|
||||
<html class="gradientBG" lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<meta http-equiv="X-UA-Compatible" content="ie=edge">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:title" content="jeff" />
|
||||
<meta property="og:description" content="A simple video downloader without any ad or tracking." />
|
||||
<meta property="og:url" content="https://namejeff.xyz/" />
|
||||
<meta property="og:image" content="https://namejeff.xyz/asset/jeff.png" />
|
||||
<meta name="theme-color" content="#3b2ccf" />
|
||||
<link rel="icon" href="/asset/favicon.ico" type="image/x-icon"/>
|
||||
<link rel="shortcut icon" href="asset/favicon.ico" type="image/x-icon"/>
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/index.css">
|
||||
<link rel="stylesheet" type="text/css" href="stylesheets/background.css">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bulma@0.8.0/css/bulma.min.css">
|
||||
<script defer src="https://use.fontawesome.com/releases/v5.3.1/js/all.js"></script>
|
||||
<title>jeff</title>
|
||||
</head>
|
||||
<body class="has-text-light gradientBG">
|
||||
<section class="section has-text-centered">
|
||||
<div class="container ">
|
||||
<h1 class="title has-text-light">Le epic downloader v<%= version %>%></h1>
|
||||
<div class="downloader form">
|
||||
<form id="download-form" method="POST" action="/" enctype="application/x-www-form-urlencoded">
|
||||
<div class="field is-horizontal">
|
||||
<div class="field-body">
|
||||
<div class="field is-horizontal">
|
||||
<div class="control">
|
||||
<label class="radio" for="small">
|
||||
<input class="radio" type="radio" name="quality" id="small" value="small">
|
||||
Low quality
|
||||
</label>
|
||||
|
||||
<label class="radio" for="high">
|
||||
<input class="radio" type="radio" name="quality" id="high" value="high" checked>
|
||||
High quality
|
||||
</label>
|
||||
|
||||
<label class="checkbox" for="alt">
|
||||
<input class="checkbox" type="checkbox" name="alt" id="alt" title="Use this if download dosen't work">
|
||||
Alternate download
|
||||
</label>
|
||||
|
||||
<label class="checkbox" for="feed">
|
||||
<input class="checkbox" type="checkbox" name="feed" id="feed" title="Use this if you don't want the video you are downloading to be public">
|
||||
Hide from feed
|
||||
</label>
|
||||
|
||||
<label class="checkbox" for="sponsorBlock">
|
||||
<input class="checkbox" type="checkbox" name="sponsorBlock" id="sponsorBlock" title="(Using sponsor.ajay.app)">
|
||||
(W.I.P) Remove sponsors of video using <a href="https://sponsor.ajay.app/">SponsorBlock</a>
|
||||
</label>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field-body">
|
||||
<div class="field is-expanded">
|
||||
<div class="field has-addons">
|
||||
<p class="control is-expanded">
|
||||
<input type="text" id="url" name="url" class="downloadurl input is-rounded" placeholder="Link">
|
||||
</p>
|
||||
<p class="control">
|
||||
<button type="submit" class="downloadbtn button is-primary is-rounded" id="button">Download that mf video</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<div class="field has-addon">
|
||||
<div class="control">
|
||||
</div>
|
||||
<div class="control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="field is-horizontal level">
|
||||
|
||||
<div class="control level-left">
|
||||
<label class="radio" for="mp4">
|
||||
<input class="radio" type="radio" name="format" value="mp4" id="mp4" checked>
|
||||
Video?
|
||||
</label>
|
||||
|
||||
<label class="radio" for="mp3">
|
||||
<input class="radio" type="radio" name="format" value="mp3" id="mp3">
|
||||
MP3?
|
||||
</label>
|
||||
|
||||
<label class="radio" for="flac" title="This is pure placebo">
|
||||
<input class="radio" type="radio" name="format" value="flac" id="flac" title="This is pure placebo">
|
||||
FLAC?
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<div class="field-body level-right">
|
||||
<div class="field is-horizontal">
|
||||
<div class="control">
|
||||
<span>Proxy options:</span>
|
||||
<label class="radio" for="none">
|
||||
<input class="radio" type="radio" name="proxy" value="none" id="none" checked>
|
||||
None
|
||||
</label>
|
||||
<% proxy.forEach(function(proxy){ %>
|
||||
<label class="radio" for="<%= proxy.ip %>">
|
||||
<input class="radio" type="radio" name="proxy" value="<%= proxy.ip %>" id="<%= proxy.ip %>">
|
||||
<%= proxy.ip.substring(0, proxy.ip.length - 5) %> - <%= proxy.country %>
|
||||
</label>
|
||||
<% }) %>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="container" id="progress"></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<% if (files != "") {%>
|
||||
<p class="title has-text-light has-text-centered">Recently downloaded videos</p>
|
||||
<section class="section">
|
||||
<div class="columns is-vcentered is-multiline fadein">
|
||||
<% files.forEach(function(file){ %>
|
||||
<div class="column hvr-grow">
|
||||
<div class="column box notification is-dark level">
|
||||
<p class="subtitle"><%= file.name %></p>
|
||||
<div>
|
||||
<figure class="is-4by3">
|
||||
<video muted loop onmouseover="this.play();" onmouseout="this.pause();this.currentTime = 0;" oncanplay="this.muted=true;" poster="<%= file.img %>" preload="metadata">
|
||||
<source src="/thumbnail/<%= file.name %>.mp4#t=0.5" >
|
||||
<img src="<%= file.img %>" title="Your browser does not support the <video> tag">
|
||||
</video>
|
||||
</figure>
|
||||
</div>
|
||||
<br>
|
||||
<div class="content">
|
||||
<div class="field has-addons is-centered">
|
||||
<p class="control">
|
||||
<a class="button is-link is-rounded" href="<%= file.location %>" download>Download<i class="fas fa-fw fa-file-download" aria-hidden="true"></i></a>
|
||||
</p>
|
||||
<p class="control">
|
||||
<button class="button is-link is-rounded" onclick="toClipboard('https:\/\/namejeff.xyz\/{{ file.location }}')">Copy to clipboard<i class="fas fa-fw fa-clipboard" aria-hidden="true"></i></button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="field is-grouped">
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag">File format</span>
|
||||
<span class="tag is-primary"><%= file.ext %></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag">File size</span>
|
||||
<span class="tag is-primary"><%= file.size%> <%= file.unit %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="control">
|
||||
<div class="tags has-addons">
|
||||
<span class="tag">Download date</span>
|
||||
<span class="tag is-primary"><%= file.date %></span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<% }) %>
|
||||
|
||||
</div>
|
||||
</section>
|
||||
<% } %>
|
||||
|
||||
</body>
|
||||
<script>
|
||||
let uuid = uuidv4();
|
||||
console.log(uuid);
|
||||
let form = document.getElementById('download-form');
|
||||
form.addEventListener("submit", function (event) {
|
||||
event.preventDefault();
|
||||
|
||||
submitDownload();
|
||||
});
|
||||
|
||||
function submitDownload() {
|
||||
document.getElementById('progress').innerHTML = '<progress class="progress is-success" id="progress-bar" max="100">0%</progress>';
|
||||
let frm = new FormData(form);
|
||||
frm.append('uuid', uuid);
|
||||
console.log(...frm);
|
||||
let xhttp = new XMLHttpRequest();
|
||||
|
||||
console.log('hi');
|
||||
let progress = setInterval(() => {
|
||||
CheckProgress();
|
||||
}, 1000);
|
||||
|
||||
// Définissez ce qui se passe si la soumission s'est opérée avec succès
|
||||
xhttp.addEventListener("load", function(event) {
|
||||
const json = JSON.parse(event.target.responseText);
|
||||
console.log(json);
|
||||
if (json.error) {
|
||||
alert(json.error);
|
||||
} else {
|
||||
const url = json.url;
|
||||
|
||||
let a = document.createElement("a");
|
||||
a.href = url;
|
||||
fileName = url.split("/").pop();
|
||||
a.download = fileName;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
|
||||
}
|
||||
|
||||
clearInterval(progress);
|
||||
setTimeout(() => {
|
||||
document.getElementById('progress').innerHTML = '';
|
||||
}, 500)
|
||||
});
|
||||
|
||||
// Definissez ce qui se passe en cas d'erreur
|
||||
xhttp.addEventListener("error", function(event) {
|
||||
clearInterval(progress);
|
||||
document.getElementById('progress').innerHTML = '';
|
||||
alert('whoops, something gone wrong');
|
||||
});
|
||||
|
||||
xhttp.open("POST", "/", true);
|
||||
xhttp.send(frm);
|
||||
}
|
||||
|
||||
function CheckProgress() {
|
||||
let xhttp = new XMLHttpRequest();
|
||||
xhttp.open("GET", `/status/${uuid}?=${Math.random()}`, true);
|
||||
xhttp.send();
|
||||
xhttp.addEventListener("load", function(event) {
|
||||
console.log(event.target.responseText);
|
||||
document.getElementsByTagName("progress")[0].value = event.target.responseText;
|
||||
document.getElementsByTagName("progress")[0].innerHTML = `${event.target.responseText}%`;
|
||||
});
|
||||
|
||||
xhttp.addEventListener("error", function(event) {
|
||||
console.error(event.target.responseText);
|
||||
});
|
||||
}
|
||||
|
||||
function uuidv4() {
|
||||
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
|
||||
var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8);
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
||||
console.log('%cWhat are you doing here 😳😳😳😳', 'font-size: 40px;');
|
||||
</script>
|
||||
</html>
|
Loading…
Reference in a new issue