Compare commits

..

No commits in common. "6fb90e6815e36bbe2b24797bd2035c6fca8cbea4" and "4ddd6fd98a54ef8871dfad12b14d5f217ffecf9c" have entirely different histories.

15 changed files with 341 additions and 508 deletions

3
.dockerignore Normal file
View file

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

16
Dockerfile Normal file
View file

@ -0,0 +1,16 @@
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,27 +1,42 @@
# Univerter
A web downloader for Youtube videos. It can be found at https://univerter.dev
A web downloader & converter for videos with no client-sided javascript. It can be found at https://univerter.dev
Currently only supporting Youtube, and not many formats. For old Univerter, which supports a variety of sites, visit https://legacy.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.
# 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. Install Dependancies
Always make sure your dependencies are up to date! Univerter's dependencies may change from time to time.
1. Clone the repository
```bash
git clone https://github.com/Violets-puragtory/YoutubeConverter
cd YoutubeConverter
```
2. Install Dependancies
- [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)
### 2. Clone the repository
```bash
git clone https://git.viois.gay/violet/Univerter
cd Univerter
```
### 3. Download NodeJS Dependencies
3. Download NodeJS Dependencies
```bash
npm install
@ -35,10 +50,22 @@ npm start
# Environment Variables
`PORT`= [Preffered Port] (Default is port 8080)
`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.)
# 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)

22
config.json Normal file
View file

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

View file

@ -1,124 +0,0 @@
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!")
}
})

View file

@ -1,18 +0,0 @@
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,2 +1,230 @@
require("./express.js")
require("./downloader.js")
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)
})

295
package-lock.json generated
View file

@ -1,15 +1,14 @@
{
"name": "univerter",
"version": "4.0.0",
"version": "3.2.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "univerter",
"version": "4.0.0",
"version": "3.2.0",
"license": "MIT",
"dependencies": {
"@distube/ytdl-core": "^4.14.4",
"child_process": "^1.0.2",
"express": "^4.18.2",
"ffmpeg-static": "^5.2.0"
@ -29,26 +28,6 @@
"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",
@ -409,65 +388,6 @@
"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",
@ -548,19 +468,6 @@
"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",
@ -612,15 +519,6 @@
"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",
@ -691,21 +589,6 @@
"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",
@ -720,12 +603,6 @@
"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",
@ -747,12 +624,6 @@
"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",
@ -777,12 +648,6 @@
"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",
@ -867,21 +732,6 @@
"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",
@ -899,24 +749,6 @@
"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",
@ -925,16 +757,6 @@
"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",
@ -969,19 +791,6 @@
"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",
@ -1263,37 +1072,6 @@
"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",
@ -1356,15 +1134,6 @@
"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",
@ -1398,11 +1167,6 @@
"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",
@ -1455,16 +1219,6 @@
"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",
@ -1473,11 +1227,6 @@
"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",
@ -1493,11 +1242,6 @@
"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",
@ -1508,11 +1252,6 @@
"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",
@ -1584,17 +1323,6 @@
"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",
@ -1609,30 +1337,11 @@
"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": "4.0.0",
"version": "3.2.0",
"description": "A web youtube converter for converting videos to mp4, mp3, and other supported formats",
"main": "index.js",
"scripts": {
@ -23,7 +23,6 @@
},
"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,12 +8,8 @@
<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 an extremely fast web downloader for Youtube videos." property="og:description" />
<meta content="Univerter is a web video downloader & converter for Youtube, TikTok, Twitter, and more." 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" />
@ -24,7 +20,7 @@
<div class="mainDiv">
<h1>Univerter<hr></h1>
<form action="/download" method="get" target="_blank">
<form action="/download" method="get">
<p style="margin-bottom: 1px;">Video URL:</p>
<input required type="url" id="url" placeholder="Enter URL" name="url">
@ -38,7 +34,7 @@
<option value="480">480p</option>
<option value="720" selected>720p</option>
<option value="1080">1080p</option>
<option value="1440">1440p (2k)</option>
<option value="1440">1440p</option>
<option value="2160">2160p (4k)</option>
</select>
</div>
@ -48,26 +44,29 @@
<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>
<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 style="display: inline-block" type="checkbox" id="adv"> -->
<!-- <p style="display: inline-block" for="adv">Enable Advanced options? (Beta)</p> -->
<br>
<input type="submit">
</form>
<!-- <br> -->
<br>
<p>Inspired by <a href="https://cobalt.tools">Cobalt</a></p>
<p>Developed by <a href="https://violets-purgatory.dev">Violet</a></p>
<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>
</div>
</body>

2
static/js/jquery.js vendored

File diff suppressed because one or more lines are too long

View file

@ -1,2 +0,0 @@
/*! 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}));

View file

@ -1,24 +0,0 @@
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,13 +76,6 @@ body {
align-items: center;
}
@media screen and (min-height: 700px) {
html,
body {
height: 100%;
}
}
.mainDiv {
width: 100%;
/* outline: 2px white solid; */
@ -93,6 +86,13 @@ 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);
}
input[type="checkbox"] {
#adv {
accent-color: var(--primary-color);
width: 1.05rem;
height: 1.05rem;
width: 1.15rem;
height: 1.15rem;
}
.error {

BIN
yt-dlp Executable file

Binary file not shown.