first commit
This commit is contained in:
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
.parcel-cache
|
||||||
|
dist
|
||||||
|
package-lock.json
|
||||||
|
node_modules
|
||||||
39
downloader.js
Normal file
39
downloader.js
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
const { spawn } = require('child_process');
|
||||||
|
const logger = require('./lib/logger');
|
||||||
|
const settings = require('./settings.json');
|
||||||
|
|
||||||
|
const download = (socket, url) => {
|
||||||
|
const ytldp = spawn('./lib/yt-dlp.exe',
|
||||||
|
['-o', `${settings.download_path}%(title)s.%(ext)s`, url]
|
||||||
|
)
|
||||||
|
|
||||||
|
ytldp.stdout.on('data', data => {
|
||||||
|
socket.emit('progress', data.toString())
|
||||||
|
console.log(data.toString())
|
||||||
|
})
|
||||||
|
ytldp.on('exit', () => {
|
||||||
|
socket.emit('progress', 'Done!')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const abortDownload = (socket) => {
|
||||||
|
const res = process.platform === 'win32' ?
|
||||||
|
spawn('taskkill', ['/IM', 'yt-dlp.exe', '/F', '/T']) :
|
||||||
|
spawn('killall', ['yt-dlp'])
|
||||||
|
res.stdout.on('data', data => {
|
||||||
|
socket.emit('progress', 'Aborted!')
|
||||||
|
console.log(data.toString())
|
||||||
|
})
|
||||||
|
logger('download', 'Aborted')
|
||||||
|
}
|
||||||
|
|
||||||
|
const kill = async () => {
|
||||||
|
return process.platform === 'win32' ?
|
||||||
|
spawn('taskkill', ['/IM', 'yt-dlp.exe', '/F', '/T']) :
|
||||||
|
spawn('killall', ['yt-dlp'])
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
download: download,
|
||||||
|
abortDownload: abortDownload
|
||||||
|
}
|
||||||
5
frontend/index.css
Normal file
5
frontend/index.css
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
body{
|
||||||
|
height: 100vh;
|
||||||
|
background-color: #202124;
|
||||||
|
color: #f1f1f1;
|
||||||
|
}
|
||||||
19
frontend/index.html
Normal file
19
frontend/index.html
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.1.3/dist/css/bootstrap.min.css" rel="stylesheet"
|
||||||
|
integrity="sha384-1BmE4kWBq78iYhFldvKuhfTAU6auU8tT94WrHftjDbrCEXSU1oBoqyl2QvZ6jIW3" crossorigin="anonymous">
|
||||||
|
<link rel="stylesheet" href="index.css">
|
||||||
|
<title>Frontend</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="root"></div>
|
||||||
|
<script type="module" src="index.js"></script>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
||||||
5
frontend/index.js
Normal file
5
frontend/index.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import ReactDOM from 'react-dom'
|
||||||
|
import { App } from './src/App'
|
||||||
|
|
||||||
|
const root = document.getElementById('root')
|
||||||
|
ReactDOM.render(<App />, root)
|
||||||
0
frontend/src/App.css
Normal file
0
frontend/src/App.css
Normal file
69
frontend/src/App.jsx
Normal file
69
frontend/src/App.jsx
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
import { io } from "socket.io-client";
|
||||||
|
import React, { useState, useEffect } from "react";
|
||||||
|
import { Container, ProgressBar, InputGroup, FormControl, Button } from "react-bootstrap";
|
||||||
|
import './App.css'
|
||||||
|
|
||||||
|
const socket = io('http://localhost:3000')
|
||||||
|
|
||||||
|
export function App() {
|
||||||
|
useEffect(() => {
|
||||||
|
socket.on('progress', data => {
|
||||||
|
setMessage(data.trim())
|
||||||
|
if (data.trim() === 'Done!') {
|
||||||
|
setHalt(false)
|
||||||
|
setProgress(0)
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
setProgress(Math.ceil(data.split(" ")[2].replace('%', '')))
|
||||||
|
} catch (error) {
|
||||||
|
console.log('finished or empty url')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
const sendUrl = () => {
|
||||||
|
setHalt(true)
|
||||||
|
console.log(url)
|
||||||
|
socket.emit('send-url', url)
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleUrlChange = (e) => {
|
||||||
|
setUrl(e.target.value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const abort = () => {
|
||||||
|
socket.emit('abort')
|
||||||
|
setHalt(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
const [progress, setProgress] = useState(0)
|
||||||
|
const [message, setMessage] = useState('')
|
||||||
|
const [halt, setHalt] = useState(false)
|
||||||
|
const [url, setUrl] = useState('')
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<div className="mt-5" />
|
||||||
|
<h1>yt-dlp web ui</h1>
|
||||||
|
|
||||||
|
<InputGroup className="mt-5">
|
||||||
|
<FormControl placeholder="youtube video url" onChange={handleUrlChange} />
|
||||||
|
</InputGroup>
|
||||||
|
|
||||||
|
<div className="mt-2">
|
||||||
|
<h6>Status</h6>
|
||||||
|
<pre id='status'>{message}</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ProgressBar now={progress} />
|
||||||
|
|
||||||
|
<Button className="my-5" variant="success" onClick={() => sendUrl()} disabled={halt}>Go!</Button>{' '}
|
||||||
|
<Button variant="danger" onClick={() => abort()}>Abort</Button>
|
||||||
|
|
||||||
|
<div className="mt-5" />
|
||||||
|
<div>
|
||||||
|
Once you close the page the download will continue in the background. It won't be possible retriving the progress though.
|
||||||
|
</div>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
19
lib/event.js
Normal file
19
lib/event.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
var Transform = require('stream').Transform;
|
||||||
|
|
||||||
|
class SSE extends Transform{
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
|
||||||
|
if (!(this instanceof SSE))
|
||||||
|
return new SSE(options);
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
Transform.call(this, options);
|
||||||
|
}
|
||||||
|
_transform(data, enc, cb) {
|
||||||
|
this.push('data: ' + data.toString('utf8') + '\n\n');
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = SSE;
|
||||||
5
lib/logger.js
Normal file
5
lib/logger.js
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
const logger = (proto, args) => {
|
||||||
|
console.log(`[${proto}] ${args}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = logger
|
||||||
BIN
lib/yt-dlp.exe
Normal file
BIN
lib/yt-dlp.exe
Normal file
Binary file not shown.
24
lib/yt-dlpCaller.js
Normal file
24
lib/yt-dlpCaller.js
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
const Readable = require('stream').Readable;
|
||||||
|
|
||||||
|
class Subscription extends Readable{
|
||||||
|
constructor(options) {
|
||||||
|
super();
|
||||||
|
if (!(this instanceof Subscription))
|
||||||
|
return new Subscription(options);
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
Readable.call(this, options);
|
||||||
|
|
||||||
|
this.value = 0;
|
||||||
|
}
|
||||||
|
_read() {
|
||||||
|
while(this.value <= 100){
|
||||||
|
this.push(String(this.value++));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
exports.subscribe = function(event, options){
|
||||||
|
return new Subscription(options);
|
||||||
|
}
|
||||||
26
package.json
Normal file
26
package.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"name": "youtube-dlp-web",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node --harmony app.js",
|
||||||
|
"dev": "nodemon app.js",
|
||||||
|
"build": "parcel build ./frontend/index.html",
|
||||||
|
"fe": "parcel ./frontend/index.html --open"
|
||||||
|
},
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"@koa/cors": "^3.1.0",
|
||||||
|
"koa": "^2.13.4",
|
||||||
|
"koa-router": "^10.1.1",
|
||||||
|
"koa-static": "^5.0.0",
|
||||||
|
"koa-views": "^7.0.2",
|
||||||
|
"parcel": "^2.0.1",
|
||||||
|
"react": "^17.0.2",
|
||||||
|
"react-bootstrap": "^2.0.2",
|
||||||
|
"react-dom": "^17.0.2",
|
||||||
|
"socket.io": "^4.3.2",
|
||||||
|
"socket.io-client": "^4.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
42
server.js
Normal file
42
server.js
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
const Koa = require('koa');
|
||||||
|
const serve = require('koa-static');
|
||||||
|
const { Server } = require('socket.io');
|
||||||
|
const path = require('path');
|
||||||
|
const { createServer } = require('http');
|
||||||
|
const cors = require('@koa/cors');
|
||||||
|
const logger = require('./lib/logger');
|
||||||
|
const { download, abortDownload } = require('./downloader');
|
||||||
|
|
||||||
|
const app = new Koa()
|
||||||
|
const server = createServer(app.callback())
|
||||||
|
const io = new Server(server, {
|
||||||
|
cors: {
|
||||||
|
origin: "*",
|
||||||
|
methods: ["GET", "POST"]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
io.on('connection', socket => {
|
||||||
|
logger('ws', 'connesso')
|
||||||
|
|
||||||
|
socket.on('send-url', args => {
|
||||||
|
logger('ws', args)
|
||||||
|
download(socket, args)
|
||||||
|
})
|
||||||
|
|
||||||
|
socket.on('abort', () => {
|
||||||
|
abortDownload(socket)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
io.on('disconnect', () => {
|
||||||
|
logger('ws', 'disconnesso')
|
||||||
|
});
|
||||||
|
|
||||||
|
app
|
||||||
|
.use(cors())
|
||||||
|
.use(serve(path.join(__dirname, 'dist')))
|
||||||
|
|
||||||
|
console.log('[koa] Server started port', 3000)
|
||||||
|
|
||||||
|
server.listen(3000)
|
||||||
3
settings.json
Normal file
3
settings.json
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"download_path": "C:\\Users\\marco\\Downloads\\"
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user