Merge dev

This commit is contained in:
bingus_violet 2024-09-19 13:23:43 -05:00
commit 6fb90e6815
15 changed files with 508 additions and 341 deletions

View file

@ -1,3 +0,0 @@
node_modules
npm-debug.log
cached

View file

@ -1,16 +0,0 @@
FROM node:20
RUN apt-get update \
&& apt install python3 pip ffmpeg -y \
&& python3 -m pip install -U yt-dlp --break-system-packages
WORKDIR /usr/src/app
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 8080
CMD [ "node", "index.js" ]

View file

@ -1,42 +1,27 @@
# Univerter
A web downloader & converter for videos with no client-sided javascript. It can be found at https://univerter.dev
A web downloader for Youtube videos. It can be found at https://univerter.dev
Currently supports a wide variety of formats and sites, but if you have any other formats you want supported, please open an issue.
Formats added to Univerter must be supported by FFmpeg
Support for sites will only be added if those sites are supported by yt-dlp.
Currently only supporting Youtube, and not many formats. For old Univerter, which supports a variety of sites, visit https://legacy.univerter.dev.
# Installation
There are 2 main ways to install Univerter:
## Docker
The docker image can be found at https://hub.docker.com/r/bingusviolet/univerter
Port 8080 (or whatever you set the environment variable to) is exposed inside the container. It can be exposed with `-p`
Example:
```bash
docker run -p 8080:8080 bingusviolet/univerter
```
This will allow it to be found on port 8080 (E.G. `localhost:8080`)
## Node
1. Clone the repository
```bash
git clone https://github.com/Violets-puragtory/YoutubeConverter
cd YoutubeConverter
```
2. Install Dependancies
### 1. Install Dependancies
Always make sure your dependencies are up to date! Univerter's dependencies may change from time to time.
- [ffmpeg](https://github.com/FFmpeg/FFmpeg)
- [node](https://github.com/nodejs/node)
- [npm](https://github.com/npm/cli)
- [yt-dlp](https://github.com/yt-dlp/yt-dlp)
3. Download NodeJS Dependencies
### 2. Clone the repository
```bash
git clone https://git.viois.gay/violet/Univerter
cd Univerter
```
### 3. Download NodeJS Dependencies
```bash
npm install
@ -50,22 +35,10 @@ npm start
# Environment Variables
`PORT`= [Preffered Port] (8080 if unspecified)
`MAX_FILESIZE` = [Max file size in MB] (Only applies to 1080p>, don't rely on this feature yet though, as it is unfinished.)
`PORT`= [Preffered Port] (Default is port 8080)
# To-Do
- [ ] Save settings (per session)
- [ ] Proper config
- [ ] Advanced options menu
- [ ] Add option for subtitles to file
- [ ] Video Searching
- [ ] Video Caching Options
- [ ] Properly cleanup unnessacary files
- [ ] Option to disable 1080p+
- [ ] Option to disable Conversion
# Instances
Here is a list of all currently public instances. If you want to add your instance to this list, please make an issue or pull request!
- https://univerter.dev (official)
- https://yt.violets-purgatory.dev (official)

View file

@ -1,22 +0,0 @@
{
"qualityOptions": [
"2160",
"1140",
"1080",
"720",
"480",
"360",
"240",
"144"
],
"formats": {
"video": [
"mp4",
"mkv"
],
"audio": [
"mp3",
"wav"
]
}
}

124
downloader.js Normal file
View file

@ -0,0 +1,124 @@
const expressManager = require("./express.js"),
path = require("path"),
fs = require("fs"),
ytdl = require("@distube/ytdl-core"),
ffmpeg = require("ffmpeg-static"),
cp = require("child_process")
var qualityLabels = [
"144",
"240",
"360",
"480",
"720",
"1080",
"1440",
"2160"
]
var audioFormats = [
"mp3"
]
expressManager.app.get("/download", async (req, res) => {
var url = req.query.url,
quality = req.query.quality,
format = req.query.format,
trimAudio = req.query.trimAudio && audioFormats.includes(format) || false
if (ytdl.validateURL(url) && qualityLabels.includes(quality)) {
var needsVideo = !audioFormats.includes(format)
var info = await ytdl.getInfo(url)
res.setHeader("Content-Disposition", `attachment; filename="${info.videoDetails.title.replace(/[^a-z0-9 ]/gi, '')}.${format}"`)
var audioFormat = ytdl.chooseFormat(info.formats, { filter: (format) => {
return format.hasAudio && !format.hasVideo
} })
var videoFormat = undefined
var qualityLabel = quality
while (videoFormat == undefined) {
for (let i = 0; i < info.formats.length; i++) {
const format = info.formats[i];
if (format.hasVideo && !format.hasAudio && format.height && format.height.toString() == qualityLabel.toString() && (format.videoCodec.includes("avc1") || format.codecs.includes("avc1"))) {
// console.log(format.videoCodec)
videoFormat = format
continue
}
}
if (qualityLabels.indexOf(qualityLabel) - 1 < 0) {
break
}
qualityLabel = qualityLabels[qualityLabels.indexOf(qualityLabel) - 1]
}
if (!videoFormat) {
videoFormat = ytdl.chooseFormat(info.formats, { filter: "videoonly" })
}
var baseArgs = [
// Remove ffmpeg's console spamming
'-loglevel', 'error',
// // Set inputs
// '-i', 'pipe:4',
// '-i', 'pipe:5',
// Map audio & video from streams
// '-map', '0:a',
// '-map', '1:v',
// Keep encoding
'-c:v', 'copy',
'-movflags','frag_keyframe+empty_moov',
'-f', format,
// Define output file
'-',
]
var inputArgs = [
'-i', 'pipe:4'
]
var mapArgs = [
'-map', '0:a'
]
var bonusArgs = []
if (needsVideo) {
inputArgs = inputArgs.concat(['-i', 'pipe:5'])
mapArgs = mapArgs.concat(['-map', '1:v'])
}
if (trimAudio) {
bonusArgs = bonusArgs.concat(['-af', 'silenceremove=1:0:-50dB'])
}
var args = inputArgs.concat(mapArgs).concat(bonusArgs).concat(baseArgs)
const ffmpegProcess = cp.spawn(ffmpeg, args, {
windowsHide: true,
stdio: [
/* Standard: stdin, stdout, stderr */
'pipe', 'pipe', 'pipe',
/* Custom: pipe:3, pipe:4, pipe:5 */
'pipe', 'pipe', 'pipe',
],
});
ffmpegProcess.stdio[1].pipe(res)
var audio = ytdl(url, { format: audioFormat || "audioonly" })
audio.pipe(ffmpegProcess.stdio[4])
if (needsVideo) {
var video = ytdl(url, { format: videoFormat || "videoonly" })
video.pipe(ffmpegProcess.stdio[5])
}
} else {
res.send("Invalid URL!")
}
})

18
express.js Normal file
View file

@ -0,0 +1,18 @@
const express = require("express"),
path = require("path")
const PORT = process.env.PORT || 8080
var staticPath = path.join(__dirname, "static")
var app = express()
app.use(express.static(staticPath))
app.listen(PORT, () => {
console.log("Univerter now listening on PORT: " + PORT)
})
module.exports = {
app
}

232
index.js
View file

@ -1,230 +1,2 @@
const fs = require('fs'),
path = require('path'),
express = require('express'),
cp = require("child_process"),
ffmpeg = require("ffmpeg-static")
const PORT = process.env.PORT || 8080
const app = express()
const MAX_FILESIZE = process.env.MAX_FILESIZE || 500
var formats = {
"mkv": "matroska"
}
var fastFormats = ["mp4", "mkv", "mp3", "wav"]
app.get("/convert", async (req, res) => {
var file = req.query.file || ""
var format = req.query.format
var url = req.query.url
var filePath = path.join(__dirname, 'downloads', file)
if (fs.existsSync(filePath)) {
var ytdlpProcess = cp.spawnSync('./yt-dlp', ['--get-filename', url])
var name = ytdlpProcess.stdout.toString()
name = name.substring(0, name.lastIndexOf("[") - 1)
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(name)}.${format}"`);
const ffmpegProcess = cp.spawn(ffmpeg, [
'-i', filePath,
'-f', formats[format] || format,
'-movflags', 'frag_keyframe+empty_moov',
'-preset','ultrafast',
'-crf', '23',
'-loglevel', 'error',
'-'
], {
stdio: [
'pipe', 'pipe', 'pipe', 'pipe', 'pipe',
],
})
ffmpegProcess.stderr.setEncoding('utf-8')
// ffmpegProcess.stderr.on('data', (data) => {
// console.log(data)
// })
// These are debugging lines to watch FFMPEG output :3
ffmpegProcess.stdio[1].pipe(res)
.on('close', () => {
fs.rmSync(filePath)
})
}
})
app.get("/download", async (req, res) => {
const url = req.query.url
const format = req.query.format || 'mp4'
const quality = req.query.quality || '720'
res.setHeader("X-Accel-Buffering", "no")
var fileName = Math.ceil(Math.random() * 100_000_000_000).toString()
var videoName = cp.spawnSync('./yt-dlp', ['-S', 'res:' + quality, '--get-filename', url]).stdout.toString()
videoName = videoName.substring(0, videoName.lastIndexOf('.'))
videoName = videoName.substring(0, videoName.lastIndexOf('[') - 1)
if (!["mp3", "wav", "ogx"].includes(format) && Number(quality) > 720) {
res.setHeader("Content-Type", "text/html")
var downloadHTML = fs.readFileSync(path.join(__dirname, 'resources/downloading.html')).toString()
res.write(downloadHTML.substring(0, downloadHTML.indexOf("{CONTENT}")))
var filePath = path.join(__dirname, 'downloads', fileName)
var ytdlpProcess = cp.spawn('./yt-dlp', [
url,
'-o', filePath,
'--max-filesize', MAX_FILESIZE + 'm',
'-S', 'res:' + quality,
'--no-playlist',
])
var lastDownload = 0
ytdlpProcess.stderr.setEncoding('utf-8')
ytdlpProcess.stderr.on('data', (data) => {
// console.log(data)
res.write(`<div class="error"><p>` + data + `</p></div>`)
})
var debounce = false
ytdlpProcess.stdout.setEncoding('utf-8')
ytdlpProcess.stdout.on('data', (data) => {
if (!debounce) {
if (data.includes("max-filesize")) {
debounce = true
res.write(`<p>Uh oh! The video you're trying to download is too large for this server's current settings ${MAX_FILESIZE}. Please try another server? (Visit main page and go to the codeberg for a list of instances!)</p>`)
}
else if (data.includes("[download]")) {
res.write(`<style>#downloading${lastDownload}{ display: none; }</style>`)
lastDownload += 1
res.write(`<p id="downloading${lastDownload}">` + data.substring(12) + `</p>`)
}
}
})
var exited = false
ytdlpProcess.on('close', () => {
var files = fs.readdirSync(path.join(__dirname, 'downloads'))
for (let index = 0; index < files.length; index++) {
const file = files[index];
if (file.includes(fileName)) {
fileName = file
filePath = path.join(__dirname, 'downloads', fileName)
}
}
if (exited) {
if (fs.existsSync(filePath)) {
fs.rmSync(filePath)
}
}
else if (fs.existsSync(filePath)) {
res.write(`<iframe src="/convert?file=${fileName}&format=${format}&url=${url}"></iframe>"`)
res.write(downloadHTML.substring(downloadHTML.indexOf("{CONTENT}") + 9) + `<meta http-equiv="Refresh" content="0; url='/'" />`, () => { res.end() })
} else {
res.write("<p>An error has occured!!! We're not exactly sure what the error is, but we cant seem to find the download file. Double check the URL, and if the URL is fine, then file an issue on codeberg. </p>")
}
})
res.on("error", () => {
if (fs.existsSync(filePath)) {
fs.rmSync(filePath)
}
exited = true
})
} else if (["mp3", "wav", "opus"].includes(format)) {
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(videoName)}.${format}"`);
var ytdlpProcess = cp.spawn('./yt-dlp', [
url,
'-x',
'-o', '-',
'--max-filesize', MAX_FILESIZE + 'm',
'--audio-format', 'mp3',
'--no-playlist',
])
const ffmpegProcess = cp.spawn(ffmpeg, [
'-i', 'pipe:3',
'-f', formats[format] || format,
'-movflags', 'frag_keyframe+empty_moov',
'-preset','ultrafast',
'-crf', '23',
'-loglevel', 'error',
'-'
], {
stdio: [
'pipe', 'pipe', 'pipe', 'pipe', 'pipe',
],
})
ytdlpProcess.stdout.pipe(ffmpegProcess.stdio[3])
ytdlpProcess.stderr.setEncoding('utf-8')
// ytdlpProcess.stderr.on('data', (data) => {
// console.log(data)
// })
ffmpegProcess.stdio[1].pipe(res)
} else {
res.setHeader('Content-Disposition', `attachment; filename="${encodeURIComponent(videoName)}.${format}"`);
var ytdlpProcess = cp.spawn('./yt-dlp', [
url,
'-o', '-',
'--max-filesize', MAX_FILESIZE + 'm',
'-S', 'res:' + quality,
'--no-playlist',
])
const ffmpegProcess = cp.spawn(ffmpeg, [
'-i', 'pipe:3',
'-f', formats[format] || format,
'-movflags', 'frag_keyframe+empty_moov',
'-c:a', 'libmp3lame',
'-preset','ultrafast',
'-crf', '23',
'-loglevel', 'error',
'-'
], {
stdio: [
'pipe', 'pipe', 'pipe', 'pipe', 'pipe',
],
})
ytdlpProcess.stdout.pipe(ffmpegProcess.stdio[3])
ytdlpProcess.stderr.setEncoding('utf-8')
// ytdlpProcess.stderr.on('data', (data) => {
// console.log(data)
// })
ffmpegProcess.stdio[1].pipe(res)
}
})
process.on('uncaughtException', (err, origin) => {
fs.writeSync(
process.stderr.fd,
`Caught exception: ${err}\n` +
`Exception origin: ${origin}`,
);
});
app.use(express.static(path.join(__dirname, 'static')))
app.listen(PORT, function () {
console.log("Hosted on port " + PORT)
})
require("./express.js")
require("./downloader.js")

295
package-lock.json generated
View file

@ -1,14 +1,15 @@
{
"name": "univerter",
"version": "3.2.0",
"version": "4.0.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "univerter",
"version": "3.2.0",
"version": "4.0.0",
"license": "MIT",
"dependencies": {
"@distube/ytdl-core": "^4.14.4",
"child_process": "^1.0.2",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0"
@ -28,6 +29,26 @@
"node": ">=6.0.0"
}
},
"node_modules/@distube/ytdl-core": {
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.14.4.tgz",
"integrity": "sha512-dHb4GW3qATIjRsS6VIhm3Pop7FdUcDFhsnyQlsPeXW7UhTPuNS0BmraKiTpFbpp0Ky+rxBQjJBfPRFsM+dT1fg==",
"license": "MIT",
"dependencies": {
"http-cookie-agent": "^6.0.5",
"m3u8stream": "^0.8.6",
"miniget": "^4.2.3",
"sax": "^1.4.1",
"tough-cookie": "^4.1.4",
"undici": "five"
},
"engines": {
"node": ">=14.0"
},
"funding": {
"url": "https://github.com/distubejs/ytdl-core?sponsor"
}
},
"node_modules/@types/node": {
"version": "10.17.60",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
@ -388,6 +409,65 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/http-cookie-agent": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.5.tgz",
"integrity": "sha512-sfZ8fDgDP3B1YB+teqSnAK1aPgBu8reUUGxSsndP2XnYN6cM29EURXWXZqQQiaRdor3B4QjpkUNfv21syaO4DA==",
"license": "MIT",
"dependencies": {
"agent-base": "^7.1.1"
},
"engines": {
"node": ">=18.0.0"
},
"funding": {
"url": "https://github.com/sponsors/3846masa"
},
"peerDependencies": {
"tough-cookie": "^4.0.0",
"undici": "^5.11.0 || ^6.0.0"
},
"peerDependenciesMeta": {
"undici": {
"optional": true
}
}
},
"node_modules/http-cookie-agent/node_modules/agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
"license": "MIT",
"dependencies": {
"debug": "^4.3.4"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/http-cookie-agent/node_modules/debug": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"license": "MIT",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/http-cookie-agent/node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"license": "MIT"
},
"node_modules/http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@ -468,6 +548,19 @@
"node": ">= 0.10"
}
},
"node_modules/m3u8stream": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz",
"integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==",
"license": "MIT",
"dependencies": {
"miniget": "^4.2.2",
"sax": "^1.2.4"
},
"engines": {
"node": ">=12"
}
},
"node_modules/media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -519,6 +612,15 @@
"node": ">= 0.6"
}
},
"node_modules/miniget": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz",
"integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA==",
"license": "MIT",
"engines": {
"node": ">=12"
}
},
"node_modules/ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -589,6 +691,21 @@
"node": ">= 0.10"
}
},
"node_modules/psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==",
"license": "MIT"
},
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"license": "MIT",
"engines": {
"node": ">=6"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@ -603,6 +720,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
"license": "MIT"
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -624,6 +747,12 @@
"node": ">= 6"
}
},
"node_modules/requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
"license": "MIT"
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -648,6 +777,12 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"node_modules/sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg==",
"license": "ISC"
},
"node_modules/send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
@ -732,6 +867,21 @@
"node": ">=0.6"
}
},
"node_modules/tough-cookie": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"license": "BSD-3-Clause",
"dependencies": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@ -749,6 +899,24 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"node_modules/undici": {
"version": "6.19.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.4.tgz",
"integrity": "sha512-i3uaEUwNdkRq2qtTRRJb13moW5HWqviu7Vl7oYRYz++uPtGHJj+x7TGjcEuwS5Mt2P4nA0U9dhIX3DdB6JGY0g==",
"license": "MIT",
"engines": {
"node": ">=18.17"
}
},
"node_modules/universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
"license": "MIT",
"engines": {
"node": ">= 4.0.0"
}
},
"node_modules/unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
@ -757,6 +925,16 @@
"node": ">= 0.8"
}
},
"node_modules/url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"license": "MIT",
"dependencies": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@ -791,6 +969,19 @@
"parse-cache-control": "^1.0.1"
}
},
"@distube/ytdl-core": {
"version": "4.14.4",
"resolved": "https://registry.npmjs.org/@distube/ytdl-core/-/ytdl-core-4.14.4.tgz",
"integrity": "sha512-dHb4GW3qATIjRsS6VIhm3Pop7FdUcDFhsnyQlsPeXW7UhTPuNS0BmraKiTpFbpp0Ky+rxBQjJBfPRFsM+dT1fg==",
"requires": {
"http-cookie-agent": "^6.0.5",
"m3u8stream": "^0.8.6",
"miniget": "^4.2.3",
"sax": "^1.4.1",
"tough-cookie": "^4.1.4",
"undici": "five"
}
},
"@types/node": {
"version": "10.17.60",
"resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz",
@ -1072,6 +1263,37 @@
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"http-cookie-agent": {
"version": "6.0.5",
"resolved": "https://registry.npmjs.org/http-cookie-agent/-/http-cookie-agent-6.0.5.tgz",
"integrity": "sha512-sfZ8fDgDP3B1YB+teqSnAK1aPgBu8reUUGxSsndP2XnYN6cM29EURXWXZqQQiaRdor3B4QjpkUNfv21syaO4DA==",
"requires": {
"agent-base": "^7.1.1"
},
"dependencies": {
"agent-base": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.1.tgz",
"integrity": "sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==",
"requires": {
"debug": "^4.3.4"
}
},
"debug": {
"version": "4.3.6",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz",
"integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==",
"requires": {
"ms": "2.1.2"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
}
}
},
"http-errors": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@ -1134,6 +1356,15 @@
"resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz",
"integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g=="
},
"m3u8stream": {
"version": "0.8.6",
"resolved": "https://registry.npmjs.org/m3u8stream/-/m3u8stream-0.8.6.tgz",
"integrity": "sha512-LZj8kIVf9KCphiHmH7sbFQTVe4tOemb202fWwvJwR9W5ENW/1hxJN6ksAWGhQgSBSa3jyWhnjKU1Fw1GaOdbyA==",
"requires": {
"miniget": "^4.2.2",
"sax": "^1.2.4"
}
},
"media-typer": {
"version": "0.3.0",
"resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz",
@ -1167,6 +1398,11 @@
"mime-db": "1.52.0"
}
},
"miniget": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/miniget/-/miniget-4.2.3.tgz",
"integrity": "sha512-SjbDPDICJ1zT+ZvQwK0hUcRY4wxlhhNpHL9nJOB2MEAXRGagTljsO8MEDzQMTFf0Q8g4QNi8P9lEm/g7e+qgzA=="
},
"ms": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
@ -1219,6 +1455,16 @@
"ipaddr.js": "1.9.1"
}
},
"psl": {
"version": "1.9.0",
"resolved": "https://registry.npmjs.org/psl/-/psl-1.9.0.tgz",
"integrity": "sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag=="
},
"punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="
},
"qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
@ -1227,6 +1473,11 @@
"side-channel": "^1.0.4"
}
},
"querystringify": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
"integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ=="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@ -1242,6 +1493,11 @@
"util-deprecate": "^1.0.1"
}
},
"requires-port": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
@ -1252,6 +1508,11 @@
"resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
"integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="
},
"sax": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.1.tgz",
"integrity": "sha512-+aWOz7yVScEGoKNd4PA10LZ8sk0A/z5+nXQG5giUO5rprX9jgYsTdov9qCchZiPIZezbZH+jRut8nPodFAX4Jg=="
},
"send": {
"version": "0.18.0",
"resolved": "https://registry.npmjs.org/send/-/send-0.18.0.tgz",
@ -1323,6 +1584,17 @@
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA=="
},
"tough-cookie": {
"version": "4.1.4",
"resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
"integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
"requires": {
"psl": "^1.1.33",
"punycode": "^2.1.1",
"universalify": "^0.2.0",
"url-parse": "^1.5.3"
}
},
"type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
@ -1337,11 +1609,30 @@
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"undici": {
"version": "6.19.4",
"resolved": "https://registry.npmjs.org/undici/-/undici-6.19.4.tgz",
"integrity": "sha512-i3uaEUwNdkRq2qtTRRJb13moW5HWqviu7Vl7oYRYz++uPtGHJj+x7TGjcEuwS5Mt2P4nA0U9dhIX3DdB6JGY0g=="
},
"universalify": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
"integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg=="
},
"unpipe": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz",
"integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ=="
},
"url-parse": {
"version": "1.5.10",
"resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
"integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
"requires": {
"querystringify": "^2.1.1",
"requires-port": "^1.0.0"
}
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",

View file

@ -1,6 +1,6 @@
{
"name": "univerter",
"version": "3.2.0",
"version": "4.0.0",
"description": "A web youtube converter for converting videos to mp4, mp3, and other supported formats",
"main": "index.js",
"scripts": {
@ -23,6 +23,7 @@
},
"homepage": "https://github.com/Violets-puragtory/YoutubeConverter#readme",
"dependencies": {
"@distube/ytdl-core": "^4.14.4",
"child_process": "^1.0.2",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0"

View file

@ -8,8 +8,12 @@
<link rel="stylesheet" href="./style.css">
<script src="./js/jquery.js"></script>
<script src="./js/js-cookie.js"></script>
<script src="./js/main.js"></script>
<meta content="Univerter" property="og:title" />
<meta content="Univerter is a web video downloader & converter for Youtube, TikTok, Twitter, and more." property="og:description" />
<meta content="Univerter is an extremely fast web downloader for Youtube videos." property="og:description" />
<meta content="https://univerter.dev/Images/Univerter%20Logo.png" property="og:image" />
<meta content="#a200ff" data-react-helmet="true" name="theme-color" />
@ -20,7 +24,7 @@
<div class="mainDiv">
<h1>Univerter<hr></h1>
<form action="/download" method="get">
<form action="/download" method="get" target="_blank">
<p style="margin-bottom: 1px;">Video URL:</p>
<input required type="url" id="url" placeholder="Enter URL" name="url">
@ -34,7 +38,7 @@
<option value="480">480p</option>
<option value="720" selected>720p</option>
<option value="1080">1080p</option>
<option value="1440">1440p</option>
<option value="1440">1440p (2k)</option>
<option value="2160">2160p (4k)</option>
</select>
</div>
@ -44,29 +48,26 @@
<select required id="format" name="format">
<option disabled>Video Formats</option>
<option value="mp4">.mp4</option>
<option value="mkv">.mkv</option>
<option disabled>Audio Formats</option>
<option value="mp3" selected>.mp3</option>
<option value="wav">.wav</option>
<option value="opus">.ogx</option>
</select>
</div>
</div>
<!-- <br> -->
<!-- <input style="display: inline-block" type="checkbox" id="adv"> -->
<!-- <p style="display: inline-block" for="adv">Enable Advanced options? (Beta)</p> -->
<div id="audioOnly">
<input style="display: inline-block" type="checkbox" id="trim" name="trimAudio">
<p style="display: inline-block" for="trim">Trim Silence (mp3)</p>
</div>
<div id="videoAndAudio">
<!-- <input style="display: inline-block" type="checkbox" id="trim" checked>
<p style="display: inline-block" for="trim">Trim BALLS (mp4 only)</p> -->
</div>
<br>
<input type="submit">
</form>
<br>
<!-- <br> -->
<p>
Powered by <a target="_blank" href="https://github.com/yt-dlp/yt-dlp/">yt-dlp</a>
<br>
Inspired by <a href="https://cobalt.tools">Cobalt</a><br>
Check out the <a href="https://beta.univerter.dev">beta!</a> (It breaks much less!)<br>
</p>
<p>Inspired by <a href="https://cobalt.tools">Cobalt</a></p>
<p>Developed by <a href="https://violets-purgatory.dev">Violet</a></p>
</div>
</body>

2
static/js/jquery.js vendored Normal file

File diff suppressed because one or more lines are too long

2
static/js/js-cookie.js Normal file
View file

@ -0,0 +1,2 @@
/*! js-cookie v3.0.5 | MIT */
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?module.exports=t():"function"==typeof define&&define.amd?define(t):(e="undefined"!=typeof globalThis?globalThis:e||self,function(){var n=e.Cookies,o=e.Cookies=t();o.noConflict=function(){return e.Cookies=n,o}}())}(this,(function(){"use strict";function e(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var o in n)e[o]=n[o]}return e}var t=function t(n,o){function r(t,r,i){if("undefined"!=typeof document){"number"==typeof(i=e({},o,i)).expires&&(i.expires=new Date(Date.now()+864e5*i.expires)),i.expires&&(i.expires=i.expires.toUTCString()),t=encodeURIComponent(t).replace(/%(2[346B]|5E|60|7C)/g,decodeURIComponent).replace(/[()]/g,escape);var c="";for(var u in i)i[u]&&(c+="; "+u,!0!==i[u]&&(c+="="+i[u].split(";")[0]));return document.cookie=t+"="+n.write(r,t)+c}}return Object.create({set:r,get:function(e){if("undefined"!=typeof document&&(!arguments.length||e)){for(var t=document.cookie?document.cookie.split("; "):[],o={},r=0;r<t.length;r++){var i=t[r].split("="),c=i.slice(1).join("=");try{var u=decodeURIComponent(i[0]);if(o[u]=n.read(c,u),e===u)break}catch(e){}}return e?o[e]:o}},remove:function(t,n){r(t,"",e({},n,{expires:-1}))},withAttributes:function(n){return t(this.converter,e({},this.attributes,n))},withConverter:function(n){return t(e({},this.converter,n),this.attributes)}},{attributes:{value:Object.freeze(o)},converter:{value:Object.freeze(n)}})}({read:function(e){return'"'===e[0]&&(e=e.slice(1,-1)),e.replace(/(%[\dA-F]{2})+/gi,decodeURIComponent)},write:function(e){return encodeURIComponent(e).replace(/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,decodeURIComponent)}},{path:"/"});return t}));

24
static/js/main.js Normal file
View file

@ -0,0 +1,24 @@
var audioFormats = ["mp3"]
function updateOptions() {
var format = $("#format").val()
if (audioFormats.includes(format)) {
$("#audioOnly > input").attr("disabled", false)
$("#videoAndAudio > input").attr("disabled", true)
} else {
$("#audioOnly > input").attr("disabled", true)
$("#videoAndAudio > input").attr("disabled", false)
}
}
$(document).ready(() => {
updateOptions()
$("#format").on("change", updateOptions)
$("#quality").val(Cookies.get("quality") || "720")
$("#trim").prop("checked", Cookies.get("trim") == "true" || false)
$("form").submit(() => {
Cookies.set("quality", $("#quality").val())
Cookies.set("trim", $("#trim").prop("checked"))
})
})

View file

@ -76,6 +76,13 @@ body {
align-items: center;
}
@media screen and (min-height: 700px) {
html,
body {
height: 100%;
}
}
.mainDiv {
width: 100%;
/* outline: 2px white solid; */
@ -86,13 +93,6 @@ iframe {
display: none;
}
@media screen and (min-height: 500px) {
html,
body {
height: 100%;
}
}
select,
input {
font-size: 1.3rem;
@ -131,10 +131,10 @@ option {
background-color: rgba(0, 0, 20);
}
#adv {
input[type="checkbox"] {
accent-color: var(--primary-color);
width: 1.15rem;
height: 1.15rem;
width: 1.05rem;
height: 1.05rem;
}
.error {

BIN
yt-dlp

Binary file not shown.