WAY more STUFF
This commit is contained in:
parent
d42d2dd9e8
commit
8b6f6e5228
8 changed files with 237 additions and 41 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -128,3 +128,6 @@ dist
|
||||||
.yarn/build-state.yml
|
.yarn/build-state.yml
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
.pnp.*
|
.pnp.*
|
||||||
|
|
||||||
|
# Cache
|
||||||
|
cache
|
118
index.js
118
index.js
|
@ -2,18 +2,33 @@ const express = require("express"),
|
||||||
path = require("path"),
|
path = require("path"),
|
||||||
fs = require("fs"),
|
fs = require("fs"),
|
||||||
ytdl = require("ytdl-core"),
|
ytdl = require("ytdl-core"),
|
||||||
bodyParser = require("body-parser")
|
bodyParser = require("body-parser"),
|
||||||
|
youtube = require("scrape-youtube")
|
||||||
|
|
||||||
const PORT = process.env.PORT || 8080
|
const PORT = process.env.PORT || 8080
|
||||||
|
|
||||||
const staticPath = path.join(__dirname, 'static')
|
const staticPath = path.join(__dirname, 'static')
|
||||||
|
|
||||||
|
const cssPath = path.join(staticPath, 'mainStyle.css')
|
||||||
|
|
||||||
const resources = path.join(__dirname, 'resources')
|
const resources = path.join(__dirname, 'resources')
|
||||||
|
|
||||||
|
const cachePath = path.join(__dirname, 'cache')
|
||||||
|
|
||||||
const playerPath = path.join(resources, 'player.html')
|
const playerPath = path.join(resources, 'player.html')
|
||||||
const cssPath = path.join(resources, 'mainStyle.css')
|
|
||||||
|
|
||||||
const cssHeader = `<style> ${fs.readFileSync(cssPath)} </style>`
|
const cssHeader = `<style> ${fs.readFileSync(cssPath)} </style>`
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
if (fs.existsSync(cachePath)) {
|
||||||
|
fs.rmSync(cachePath, { recursive: true, force: true })
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(cachePath)
|
||||||
|
|
||||||
|
var videoCache = {}
|
||||||
|
|
||||||
var app = express()
|
var app = express()
|
||||||
|
|
||||||
app.use(bodyParser.urlencoded({ extended: false }))
|
app.use(bodyParser.urlencoded({ extended: false }))
|
||||||
|
@ -25,13 +40,17 @@ app.listen(PORT, () => {
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get("/video", async (req, res) => {
|
app.get("/video", async (req, res) => {
|
||||||
|
|
||||||
var id = req.query.q || req.query.v
|
var id = req.query.q || req.query.v
|
||||||
var range = req.headers.range
|
var range = req.headers.range
|
||||||
console.log(req.headers.range)
|
|
||||||
|
|
||||||
res.setHeader("X-Accel-Buffering", "no")
|
res.setHeader("X-Accel-Buffering", "no")
|
||||||
|
|
||||||
if (!ytdl.validateID(id) && !ytdl.validateURL(id)) {
|
if (ytdl.validateURL(id)) {
|
||||||
|
id = ytdl.getVideoID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ytdl.validateID(id)) {
|
||||||
res.setHeader("Content-Type", "text/html")
|
res.setHeader("Content-Type", "text/html")
|
||||||
res.write("Not a valid video id or url!")
|
res.write("Not a valid video id or url!")
|
||||||
res.end()
|
res.end()
|
||||||
|
@ -41,31 +60,69 @@ app.get("/video", async (req, res) => {
|
||||||
res.setHeader("Content-Type", "video/mp4")
|
res.setHeader("Content-Type", "video/mp4")
|
||||||
|
|
||||||
if (range) {
|
if (range) {
|
||||||
const video = ytdl(id, { format: 'mp4' })
|
function ready(vidpath) {
|
||||||
video.on("info", (vidinfo, dlinfo) => {
|
const fileSize = videoCache[id].size
|
||||||
|
// const fileSize = fs.statSync(vidpath).size + 1
|
||||||
const fileSize = dlinfo.contentLength
|
|
||||||
const parts = range.replace(/bytes=/, "").split("-")
|
const parts = range.replace(/bytes=/, "").split("-")
|
||||||
const start = parseInt(parts[0], 10)
|
const start = parseInt(parts[0], 10)
|
||||||
const end = parts[1]
|
const end = parts[1]
|
||||||
? parseInt(parts[1], 10)
|
? parseInt(parts[1], 10)
|
||||||
: fileSize - 1
|
: fileSize - 1
|
||||||
|
|
||||||
if (start >= fileSize) {
|
if (start >= fs.statSync(vidpath).size + 1) {
|
||||||
|
console.log("AAAAAAAAA")
|
||||||
res.status(416).send('Requested range not satisfiable\n' + start + ' >= ' + fileSize);
|
res.status(416).send('Requested range not satisfiable\n' + start + ' >= ' + fileSize);
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const chunksize = (end - start) + 1
|
const chunksize = (end - start) + 1
|
||||||
// const chunksize = 6585810944
|
|
||||||
// console.log(start, end)
|
|
||||||
|
|
||||||
res.setHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`)
|
const head = {
|
||||||
res.setHeader("Accept-Ranges", 'bytes')
|
'Content-Range': `bytes ${start}-${end}/${fileSize}`,
|
||||||
res.setHeader("Content-Length", chunksize)
|
'Accept-Ranges': 'bytes',
|
||||||
|
'Content-Length': chunksize,
|
||||||
|
'Content-Type': 'video/mp4',
|
||||||
|
}
|
||||||
|
|
||||||
|
res.writeHead(206, head)
|
||||||
|
|
||||||
|
fs.createReadStream(vidpath, { start: start }).pipe(res)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id in videoCache) {
|
||||||
|
ready(videoCache[id].path)
|
||||||
|
} else {
|
||||||
|
vidpath = path.join(__dirname, `cache/${id}.mp4`)
|
||||||
|
|
||||||
|
var debounce = true
|
||||||
|
|
||||||
|
var dp = 0
|
||||||
|
ytdl(id, { filter: "videoandaudio", quality: "highest", format: 'mp4' })
|
||||||
|
.on("progress", (chunk, ct, et) => {
|
||||||
|
if (debounce) {
|
||||||
|
debounce = false
|
||||||
|
videoCache[id] = {
|
||||||
|
"path": vidpath,
|
||||||
|
"size": et,
|
||||||
|
"downloaded": false,
|
||||||
|
"download%": 0
|
||||||
|
}
|
||||||
|
ready(vidpath, fs.readFileSync(vidpath))
|
||||||
|
}
|
||||||
|
var percent = Math.round(ct / et * 100)
|
||||||
|
if (percent > dp) {
|
||||||
|
dp = percent
|
||||||
|
videoCache[id]["download%"] = dp
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.on("finish", () => {
|
||||||
|
videoCache[id]["downloaded"] = true
|
||||||
|
})
|
||||||
|
.pipe(fs.createWriteStream(vidpath))
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
video.pipe(res)
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
const head = {
|
const head = {
|
||||||
'Content-Length': fileSize,
|
'Content-Length': fileSize,
|
||||||
|
@ -77,10 +134,21 @@ app.get("/video", async (req, res) => {
|
||||||
})
|
})
|
||||||
|
|
||||||
app.get("/watch", async (req, res) => {
|
app.get("/watch", async (req, res) => {
|
||||||
const id = req.query.q || req.query.v
|
var id = req.query.q || req.query.v
|
||||||
|
|
||||||
res.setHeader("Content-Type", "text/html")
|
res.setHeader("Content-Type", "text/html")
|
||||||
|
|
||||||
|
if (ytdl.validateURL(id)) {
|
||||||
|
id = ytdl.getVideoID(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!ytdl.validateID(id)) {
|
||||||
|
res.setHeader("Content-Type", "text/html")
|
||||||
|
res.write("Not a valid video id or url!")
|
||||||
|
res.end()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
var vidInfo = (await ytdl.getBasicInfo(id)).videoDetails
|
var vidInfo = (await ytdl.getBasicInfo(id)).videoDetails
|
||||||
|
|
||||||
var html = fs.readFileSync(playerPath).toString()
|
var html = fs.readFileSync(playerPath).toString()
|
||||||
|
@ -93,6 +161,20 @@ app.get("/watch", async (req, res) => {
|
||||||
|
|
||||||
html = html.replace("{VIDEO_DESCRIPTION}", vidInfo.description || "No Description.")
|
html = html.replace("{VIDEO_DESCRIPTION}", vidInfo.description || "No Description.")
|
||||||
|
|
||||||
|
if (!(id in videoCache && videoCache[id]["downloaded"] == true)) {
|
||||||
|
html = html.replace("{CACHE_WARNING}", `
|
||||||
|
<p style="color: lightgray">Please note that this video has not been fully cached, and may have trouble loading!
|
||||||
|
<br>{DOWNLOAD_PERCENT}% cached.</p>
|
||||||
|
`)
|
||||||
|
if (id in videoCache && "download%" in videoCache[id]) {
|
||||||
|
html = html.replace("{DOWNLOAD_PERCENT}", videoCache[id]["download%"])
|
||||||
|
} else {
|
||||||
|
html = html.replace("{DOWNLOAD_PERCENT}", "0")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
html = html.replace("{CACHE_WARNING}", "")
|
||||||
|
}
|
||||||
|
|
||||||
var finalThumb = vidInfo.thumbnails[vidInfo.thumbnails.length - 1].url
|
var finalThumb = vidInfo.thumbnails[vidInfo.thumbnails.length - 1].url
|
||||||
html = html.replace("{VIDEO_THUMBNAIL}", finalThumb)
|
html = html.replace("{VIDEO_THUMBNAIL}", finalThumb)
|
||||||
|
|
||||||
|
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -11,6 +11,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"scrape-youtube": "^2.4.0",
|
||||||
"ytdl-core": "^4.11.5"
|
"ytdl-core": "^4.11.5"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -591,6 +592,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.3.0.tgz",
|
||||||
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
|
"integrity": "sha512-0s+oAmw9zLl1V1cS9BtZN7JAd0cW5e0QH4W3LWEK6a4LaLEA2OTpGYWDY+6XasBLtz6wkm3u1xRw95mRuJ59WA=="
|
||||||
},
|
},
|
||||||
|
"node_modules/scrape-youtube": {
|
||||||
|
"version": "2.4.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/scrape-youtube/-/scrape-youtube-2.4.0.tgz",
|
||||||
|
"integrity": "sha512-fUmtg2Fa8xKSGW3S7BvQwaHGxeFUeTtIsHU/AEQcBYQfCcJfDIVJeX1jtBuPOYqy3VaBVDqwdCpSYfMIHjGDEQ=="
|
||||||
|
},
|
||||||
"node_modules/send": {
|
"node_modules/send": {
|
||||||
"version": "0.18.0",
|
"version": "0.18.0",
|
||||||
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
|
||||||
|
|
|
@ -19,6 +19,7 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"body-parser": "^1.20.2",
|
"body-parser": "^1.20.2",
|
||||||
"express": "^4.18.2",
|
"express": "^4.18.2",
|
||||||
|
"scrape-youtube": "^2.4.0",
|
||||||
"ytdl-core": "^4.11.5"
|
"ytdl-core": "^4.11.5"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
body {
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
#videoPlayer {
|
|
||||||
margin: auto;
|
|
||||||
padding: 0;
|
|
||||||
object-fit: contain;
|
|
||||||
display: block;
|
|
||||||
height: 100%;
|
|
||||||
max-width: 100%;
|
|
||||||
/* height: 80vh; */
|
|
||||||
}
|
|
||||||
|
|
||||||
#videoContainer {
|
|
||||||
height: 80%;
|
|
||||||
max-width: 100vw;
|
|
||||||
display: flex;
|
|
||||||
background-color: black;
|
|
||||||
}
|
|
|
@ -1,5 +1,6 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
@ -8,14 +9,25 @@
|
||||||
|
|
||||||
<title>Document</title>
|
<title>Document</title>
|
||||||
</head>
|
</head>
|
||||||
|
|
||||||
<body>
|
<body>
|
||||||
|
<div id="titleBar">
|
||||||
|
<h1><a href="/">Simpletube</a></h1>
|
||||||
|
</div>
|
||||||
<div id="videoContainer">
|
<div id="videoContainer">
|
||||||
<video id="videoPlayer" controls muted="muted" autoplay poster="{VIDEO_THUMBNAIL}">
|
<video id="videoPlayer" controls autoplay poster="{VIDEO_THUMBNAIL}">
|
||||||
<source src="/video?q={VIDEOID}" type="video/mp4">
|
<source src="/video?q={VIDEOID}" type="video/mp4">
|
||||||
</video>
|
</video>
|
||||||
</div>
|
</div>
|
||||||
<hr>
|
<hr>
|
||||||
<h2 id="title">{VIDEO_TITLE}</h2>
|
<main>
|
||||||
<p id="description">{VIDEO_DESCRIPTION}</p>
|
<h2 id="title">{VIDEO_TITLE}</h2>
|
||||||
|
{CACHE_WARNING}
|
||||||
|
<div id="description">
|
||||||
|
<h2>Description: <br></h2>
|
||||||
|
<p id="description">{VIDEO_DESCRIPTION}</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
33
static/index.html
Normal file
33
static/index.html
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
|
||||||
|
<link rel="stylesheet" href="./mainStyle.css">
|
||||||
|
|
||||||
|
<title>SimpleTube</title>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body>
|
||||||
|
<div id="titleBar">
|
||||||
|
<h1>SimpleTube</h1>
|
||||||
|
</div>
|
||||||
|
<main>
|
||||||
|
<h1>Welcome to SimpleTube</h1>
|
||||||
|
<p>SimpleTube is a Youtube client that aims to be free of Javascript, as small as possible, and lack annoyances.
|
||||||
|
<br>
|
||||||
|
Currently SimpleTube does not have a trending tab or anything, so please use the search function at the top!
|
||||||
|
<br>
|
||||||
|
(Sorry!)</p>
|
||||||
|
<h2>More about the project: <br></h2>
|
||||||
|
<p>
|
||||||
|
<a href="https://github.com/Violets-puragtory/SimpleTube">Github</a>
|
||||||
|
<a href="violets-purgatory.dev">The developer</a>
|
||||||
|
</p>
|
||||||
|
|
||||||
|
</main>
|
||||||
|
</body>
|
||||||
|
|
||||||
|
</html>
|
79
static/mainStyle.css
Normal file
79
static/mainStyle.css
Normal file
|
@ -0,0 +1,79 @@
|
||||||
|
body {
|
||||||
|
height: 100vh;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
background-color: rgb(40, 30, 50);
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
color: white;
|
||||||
|
font-family: Verdana, Geneva, Tahoma, sans-serif;
|
||||||
|
line-height: 2.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a:not(h1 > a) {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
background-color: black;
|
||||||
|
padding: 8px;
|
||||||
|
margin: 5px;
|
||||||
|
border: 2px white solid;
|
||||||
|
border-radius: 15px;
|
||||||
|
color: rgb(240, 220, 255);
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1:not(#titleBar > h1), h2, p, a {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#titleBar {
|
||||||
|
background-color: rgb(5, 0, 10);
|
||||||
|
/* border: 0px; */
|
||||||
|
border: 2px transparent solid;
|
||||||
|
border-bottom-color: white;
|
||||||
|
position: sticky;
|
||||||
|
top: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#titleBar > h1 {
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#titleBar > h1 > a {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#description {
|
||||||
|
background-color: rgb(12, 10, 15);
|
||||||
|
padding: 10px;
|
||||||
|
border: 2px gray solid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#videoPlayer {
|
||||||
|
margin: auto;
|
||||||
|
padding: 0;
|
||||||
|
object-fit: contain;
|
||||||
|
display: block;
|
||||||
|
/* height: 100%;
|
||||||
|
max-width: 100%; */
|
||||||
|
width: 100%;
|
||||||
|
aspect-ratio: 1.8/1;
|
||||||
|
max-height: 80vh;
|
||||||
|
/* height: 80vh; */
|
||||||
|
}
|
||||||
|
|
||||||
|
#videoContainer {
|
||||||
|
max-width: 100vw;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
Loading…
Reference in a new issue