{VIDEO_TITLE}
+ {CACHE_WARNING} +Description:
+ {VIDEO_DESCRIPTION}
+diff --git a/.gitignore b/.gitignore index c6bba59..f5e3ff0 100644 --- a/.gitignore +++ b/.gitignore @@ -128,3 +128,6 @@ dist .yarn/build-state.yml .yarn/install-state.gz .pnp.* + +# Cache +cache \ No newline at end of file diff --git a/index.js b/index.js index dca4773..193039f 100644 --- a/index.js +++ b/index.js @@ -2,18 +2,33 @@ const express = require("express"), path = require("path"), fs = require("fs"), ytdl = require("ytdl-core"), - bodyParser = require("body-parser") + bodyParser = require("body-parser"), + youtube = require("scrape-youtube") const PORT = process.env.PORT || 8080 const staticPath = path.join(__dirname, 'static') + +const cssPath = path.join(staticPath, 'mainStyle.css') + const resources = path.join(__dirname, 'resources') +const cachePath = path.join(__dirname, 'cache') + const playerPath = path.join(resources, 'player.html') -const cssPath = path.join(resources, 'mainStyle.css') const cssHeader = `` + + +if (fs.existsSync(cachePath)) { + fs.rmSync(cachePath, { recursive: true, force: true }) +} + +fs.mkdirSync(cachePath) + +var videoCache = {} + var app = express() app.use(bodyParser.urlencoded({ extended: false })) @@ -25,13 +40,17 @@ app.listen(PORT, () => { }) app.get("/video", async (req, res) => { + var id = req.query.q || req.query.v var range = req.headers.range - console.log(req.headers.range) 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.write("Not a valid video id or url!") res.end() @@ -41,31 +60,69 @@ app.get("/video", async (req, res) => { res.setHeader("Content-Type", "video/mp4") if (range) { - const video = ytdl(id, { format: 'mp4' }) - video.on("info", (vidinfo, dlinfo) => { - - const fileSize = dlinfo.contentLength + function ready(vidpath) { + const fileSize = videoCache[id].size + // const fileSize = fs.statSync(vidpath).size + 1 const parts = range.replace(/bytes=/, "").split("-") const start = parseInt(parts[0], 10) const end = parts[1] ? parseInt(parts[1], 10) : 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); return } const chunksize = (end - start) + 1 - // const chunksize = 6585810944 - // console.log(start, end) - res.setHeader("Content-Range", `bytes ${start}-${end}/${fileSize}`) - res.setHeader("Accept-Ranges", 'bytes') - res.setHeader("Content-Length", chunksize) + const head = { + 'Content-Range': `bytes ${start}-${end}/${fileSize}`, + '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 { const head = { 'Content-Length': fileSize, @@ -77,10 +134,21 @@ app.get("/video", 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") + 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 html = fs.readFileSync(playerPath).toString() @@ -93,6 +161,20 @@ app.get("/watch", async (req, res) => { html = html.replace("{VIDEO_DESCRIPTION}", vidInfo.description || "No Description.") + if (!(id in videoCache && videoCache[id]["downloaded"] == true)) { + html = html.replace("{CACHE_WARNING}", ` +
Please note that this video has not been fully cached, and may have trouble loading!
+
{DOWNLOAD_PERCENT}% cached.
{VIDEO_DESCRIPTION}
+{VIDEO_DESCRIPTION}
+SimpleTube is a Youtube client that aims to be free of Javascript, as small as possible, and lack annoyances.
+
+ Currently SimpleTube does not have a trending tab or anything, so please use the search function at the top!
+
+ (Sorry!)
+ Github + The developer +
+ +