mirror of
https://github.com/jely2002/youtube-dl-gui.git
synced 2021-11-01 22:46:21 +03:00
Initial commit (1.0.0)
This commit is contained in:
BIN
bin/ffmpeg.exe
Normal file
BIN
bin/ffmpeg.exe
Normal file
Binary file not shown.
BIN
bin/icon-light.png
Normal file
BIN
bin/icon-light.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
BIN
bin/icon.png
Normal file
BIN
bin/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 9.6 KiB |
BIN
bin/waiting-for-link.png
Normal file
BIN
bin/waiting-for-link.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 8.1 KiB |
BIN
build/icon.ico
Normal file
BIN
build/icon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 101 KiB |
140
custom.css
Normal file
140
custom.css
Normal file
@@ -0,0 +1,140 @@
|
||||
body {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: #212121;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.container-fluid {
|
||||
padding: 10px 30px;
|
||||
}
|
||||
|
||||
.menubar {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.main-input {
|
||||
background-color: #303030;
|
||||
padding: 30px;
|
||||
margin-top: 20px;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
#main_input label, .header {
|
||||
font-size: 22px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#main_input .form-group {
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
|
||||
.thumbnail {
|
||||
border-radius: 3px;
|
||||
width: 100%;
|
||||
height: 21vw;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.title, .channel, .duration {
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.spinner-border {
|
||||
display: none;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
width: 3rem;
|
||||
height: 3rem;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.bs-stepper {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.bs-stepper .line {
|
||||
min-height: 2px;
|
||||
background-color: rgba(0,0,0,.25);
|
||||
}
|
||||
|
||||
.active .bs-stepper-circle {
|
||||
background-color: #5cb85c !important;
|
||||
}
|
||||
|
||||
/* CUSTOM CHECKMARK SPINNER */
|
||||
.circle-loader {
|
||||
margin-bottom: 3.5em;
|
||||
border: 2px solid rgba(0, 0, 0, 0.2);
|
||||
border-left-color: #5cb85c;
|
||||
animation: loader-spin 1.2s infinite linear;
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
vertical-align: top;
|
||||
border-radius: 50%;
|
||||
width: 7em;
|
||||
height: 7em;
|
||||
}
|
||||
.load-complete {
|
||||
-webkit-animation: none;
|
||||
animation: none;
|
||||
border-color: #5cb85c;
|
||||
transition: border 500ms ease-out;
|
||||
}
|
||||
.checkmark {
|
||||
display: none;
|
||||
}
|
||||
.checkmark.draw:after {
|
||||
animation-duration: 800ms;
|
||||
animation-timing-function: ease;
|
||||
animation-name: checkmark;
|
||||
transform: scaleX(-1) rotate(135deg);
|
||||
}
|
||||
.checkmark:after {
|
||||
opacity: 1;
|
||||
height: 3.5em;
|
||||
width: 1.75em;
|
||||
transform-origin: left top;
|
||||
border-right: 4px solid #5cb85c;
|
||||
border-top: 4px solid #5cb85c;
|
||||
content: '';
|
||||
left: 1.65em;
|
||||
top: 3.5em;
|
||||
position: absolute;
|
||||
}
|
||||
@keyframes loader-spin {
|
||||
0% {
|
||||
transform: rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: rotate(360deg);
|
||||
}
|
||||
}
|
||||
@keyframes checkmark {
|
||||
0% {
|
||||
height: 0;
|
||||
width: 0;
|
||||
opacity: 1;
|
||||
}
|
||||
20% {
|
||||
height: 0;
|
||||
width: 1.75em;
|
||||
opacity: 1;
|
||||
}
|
||||
40% {
|
||||
height: 3.5em;
|
||||
width: 1.75em;
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
height: 3.5em;
|
||||
width: 1.75em;
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
119
downloader.js
Normal file
119
downloader.js
Normal file
@@ -0,0 +1,119 @@
|
||||
const youtubedl = require('youtube-dl')
|
||||
const {remote} = require('electron')
|
||||
|
||||
let selectedURL
|
||||
let availableFormats = []
|
||||
|
||||
youtubedl.setYtdlBinary("resources/app.asar.unpacked/node_modules/youtube-dl/bin/youtube-dl.exe")
|
||||
|
||||
function settings() {
|
||||
stepper.next()
|
||||
selectedURL = $("#url").val()
|
||||
}
|
||||
|
||||
function url_entered() {
|
||||
let url = $("#url").val()
|
||||
if(validate(url)) {
|
||||
showInfo(url)
|
||||
$('#url').addClass("is-valid").removeClass("is-invalid")
|
||||
} else {
|
||||
$('#url').addClass("is-invalid").removeClass("is-valid")
|
||||
}
|
||||
}
|
||||
|
||||
function validate(url) {
|
||||
const regex = /(?:youtube\.com\/(?:[^\/]+\/.+\/|(?:v|e(?:mbed)?)\/|.*[?&]v=)|youtu\.be\/)([^"&?\/\s]{11})/gi
|
||||
return (regex.test(url))
|
||||
}
|
||||
|
||||
function showInfo(url) {
|
||||
$(".spinner-border").css("display", "inherit");
|
||||
youtubedl.getInfo(url, function(err, info) {
|
||||
if (err) throw err
|
||||
$(".thumbnail").attr("src", info.thumbnail)
|
||||
$(".title").html("<strong>Title:</strong> " + info.title)
|
||||
$(".channel").html("<strong>Channel:</strong> " + info.uploader)
|
||||
$(".duration").html("<strong>Duration:</strong> " + info.duration)
|
||||
$(".spinner-border").css("display", "none")
|
||||
$('#step-one-btn').prop("disabled", false)
|
||||
});
|
||||
selectedURL = url
|
||||
youtubedl.exec(selectedURL, ['-F','--skip-download'], {}, function(err, output) {
|
||||
if (err) throw err
|
||||
output.splice(0,3)
|
||||
console.log(output)
|
||||
output.forEach(function(entry) {
|
||||
if(!(entry.includes('mp4'))) return
|
||||
if(entry.includes('4320p') && !availableFormats.includes('4320p')) availableFormats.push('4320p')
|
||||
if(entry.includes('2160p') && !availableFormats.includes('2160p')) availableFormats.push('2160p')
|
||||
if(entry.includes('1440p') && !availableFormats.includes('1440p')) availableFormats.push('1440p')
|
||||
if(entry.includes('1080p') && !availableFormats.includes('1080p')) availableFormats.push('1080p')
|
||||
if(entry.includes('720p') && !availableFormats.includes('720p')) availableFormats.push('720p')
|
||||
if(entry.includes('480p') && !availableFormats.includes('480p')) availableFormats.push('480p')
|
||||
if(entry.includes('360p') && !availableFormats.includes('360p')) availableFormats.push('360p')
|
||||
if(entry.includes('240p') && !availableFormats.includes('240p')) availableFormats.push('240p')
|
||||
if(entry.includes('144p') && !availableFormats.includes('144p')) availableFormats.push('144p')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function downloadAudio(quality) {
|
||||
const options = [
|
||||
'-f', quality + 'audio[ext=m4a]',
|
||||
'--ffmpeg-location', 'bin/ffmpeg.exe', '--hls-prefer-ffmpeg',
|
||||
'-o', remote.app.getPath('music').replace(/\\/g, "/") + '/' + '%(title)s.%(ext)s'
|
||||
]
|
||||
|
||||
youtubedl.exec(selectedURL, options, {}, function(err, output) {
|
||||
if (err) throw err
|
||||
downloadFinished()
|
||||
console.log(output)
|
||||
})
|
||||
}
|
||||
|
||||
function downloadVideo(quality) {
|
||||
const options = [
|
||||
'-f', 'bestvideo[height<='+ quality + ',ext=mp4]+bestaudio[ext=m4a]/best[height<=' + quality + ']',
|
||||
'--ffmpeg-location', 'ffmpeg.exe', '--hls-prefer-ffmpeg',
|
||||
'--merge-output-format', 'mp4',
|
||||
'-o', remote.app.getPath('videos').replace(/\\/g, "/") + '/' + '%(title)s.%(ext)s'
|
||||
]
|
||||
|
||||
youtubedl.exec(selectedURL, options, {}, function(err, output) {
|
||||
if (err) throw err
|
||||
downloadFinished()
|
||||
console.log(output)
|
||||
})
|
||||
}
|
||||
|
||||
function download() {
|
||||
let quality = $('#quality').val()
|
||||
stepper.next()
|
||||
if($('input[name=type-select]:checked').val() === "video") {
|
||||
downloadVideo(quality)
|
||||
} else {
|
||||
downloadAudio(quality)
|
||||
}
|
||||
}
|
||||
|
||||
function downloadFinished() {
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
$('#reset-btn').html("Download another video").prop("disabled", false)
|
||||
}
|
||||
|
||||
function resetSteps() {
|
||||
selectedURL = ""
|
||||
availableFormats = []
|
||||
$('#url').removeClass("is-valid").removeClass("is-invalid")
|
||||
$(".thumbnail").attr("src", "https://via.placeholder.com/640x360?text=%20")
|
||||
$(".title").html("<strong>Title:</strong> --")
|
||||
$(".channel").html("<strong>Channel:</strong> --")
|
||||
$(".duration").html("<strong>Duration:</strong> --")
|
||||
$('.main-input').trigger('reset')
|
||||
$('.circle-loader').toggleClass('load-complete')
|
||||
$('.checkmark').toggle()
|
||||
$('#reset-btn').html("Downloading...").prop("disabled", true)
|
||||
$('#quality').empty().append(new Option("Select quality", "quality")).prop("disabled", true).val("quality")
|
||||
stepper.reset()
|
||||
}
|
||||
120
index.html
Normal file
120
index.html
Normal file
@@ -0,0 +1,120 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Youtube Downloader</title>
|
||||
<link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/css/bootstrap.min.css" integrity="sha384-Vkoo8x4CGsO3+Hhxv8T/Q5PaXtkKtu6ug5TOeNV6gBiFeWPGFN9MuhOf23Q9Ifjh" crossorigin="anonymous">
|
||||
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bs-stepper/dist/css/bs-stepper.min.css">
|
||||
<link rel="stylesheet" href="node_modules/@fortawesome/fontawesome-free/css/all.min.css">
|
||||
<link rel="stylesheet" href="custom.css" crossorigin="anonymous">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container-fluid text-center">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="bs-stepper">
|
||||
<div class="bs-stepper-header" role="tablist">
|
||||
<div class="step" data-target="#url-part">
|
||||
<button type="button" class="step-trigger" role="tab" aria-controls="url-part" id="url-part-trigger">
|
||||
<span class="bs-stepper-circle"><i class="fas fa-link"></i></span>
|
||||
<span class="bs-stepper-label">YouTube URL</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="step" data-target="#settings-part">
|
||||
<button type="button" class="step-trigger" role="tab" aria-controls="settings-part" id="settings-part-trigger">
|
||||
<span class="bs-stepper-circle"><i class="fas fa-cog"></i></span>
|
||||
<span class="bs-stepper-label">Settings</span>
|
||||
</button>
|
||||
</div>
|
||||
<div class="line"></div>
|
||||
<div class="step" data-target="#download-part">
|
||||
<button type="button" class="step-trigger" role="tab" aria-controls="download-part" id="download-part-trigger">
|
||||
<span class="bs-stepper-circle"><i class="fas fa-download"></i></span>
|
||||
<span class="bs-stepper-label">Download</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bs-stepper-content">
|
||||
<div id="url-part" class="content fade" role="tabpanel" aria-labelledby="url-part-trigger">
|
||||
<form class="main-input">
|
||||
<div class="form-group">
|
||||
<input type="text" oninput="url_entered($(this).val())" class="form-control" id="url" placeholder="YouTube link ex. youtube.com/watch?v=FtveSk1N7Uo" required>
|
||||
<div class="invalid-feedback">
|
||||
Please enter a valid YouTube URL.
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<div class="spinner-border text-dark" role="status">
|
||||
<span class="sr-only">Loading...</span>
|
||||
</div>
|
||||
<img src="bin/waiting-for-link.png" class="img-fluid thumbnail" alt="Thumbnail of the specified video">
|
||||
</div>
|
||||
<div class="col-md-6 text-left">
|
||||
<p class="title"><strong>Title:</strong> --</p>
|
||||
<p class="channel"><strong>Channel:</strong> --</p>
|
||||
<p class="duration"><strong>Duration:</strong> --</p>
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<button type="button" id="step-one-btn" onclick="settings()" class="btn btn-dark mt-2 mb-0" disabled>Next</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="settings-part" class="content fade" role="tabpanel" aria-labelledby="settings-part-trigger">
|
||||
<form class="main-input">
|
||||
<div class="row">
|
||||
<div class="col-md-6">
|
||||
<img src="bin/waiting-for-link.png" class="img-fluid thumbnail" alt="Thumbnail of the specified video">
|
||||
</div>
|
||||
<div class="col-md-6 text-left">
|
||||
<div class="form-group">
|
||||
<form id="settings">
|
||||
<div class="custom-control custom-radio custom-control-inline">
|
||||
<input type="radio" id="video" onclick="setType('video')" value="video" name="type-select" class="custom-control-input">
|
||||
<label class="custom-control-label" for="video">Video</label>
|
||||
</div>
|
||||
<div class="custom-control custom-radio custom-control-inline">
|
||||
<input type="radio" onclick="setType('audio')" id="audio" value="audio" name="type-select" class="custom-control-input">
|
||||
<label class="custom-control-label" for="audio">Audio</label>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label for="quality"></label>
|
||||
<select id="quality" class="custom-select" disabled>
|
||||
<option selected>Select quality</option>
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<button type="button" onclick="stepper.reset()" class="btn btn-dark">Back</button>
|
||||
<button type="button" onclick="download()" class="btn btn-dark">Download</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div id="download-part" class="content fade" role="tabpanel" aria-labelledby="download-part-trigger">
|
||||
<div class="main-input">
|
||||
<div class="row justify-content-center">
|
||||
<div class="circle-loader">
|
||||
<div class="checkmark draw"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row justify-content-center">
|
||||
<button type="button" id="reset-btn" onclick="resetSteps()" class="btn btn-dark mt-2 mb-0 col-md-4" disabled>Downloading...</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script src="plugins.js"></script>
|
||||
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.4.1/js/bootstrap.min.js" integrity="sha384-wfSDF2E50Y2D1uUdj0O3uMBJnjuUD4Ih7YwaYd1iqfktj0Uod8GCExl3Og8ifwB6" crossorigin="anonymous"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/bs-stepper@1.7.0/dist/js/bs-stepper.min.js" integrity="sha256-INfYp5owpb0btFquNHGlhSxgGYrFlGYRU2oN/3jWGeM=" crossorigin="anonymous"></script>
|
||||
<script src="downloader.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
43
main.js
Normal file
43
main.js
Normal file
@@ -0,0 +1,43 @@
|
||||
const { app, BrowserWindow } = require('electron')
|
||||
let win
|
||||
|
||||
function createWindow () {
|
||||
app.allowRendererProcessReuse = true
|
||||
|
||||
win = new BrowserWindow({
|
||||
show: false,
|
||||
width: 800, //850
|
||||
height: 500, //550
|
||||
resizable: false,
|
||||
frame: false,
|
||||
icon: "bin/icon-light.png",
|
||||
webPreferences: {
|
||||
nodeIntegration: true
|
||||
}
|
||||
})
|
||||
|
||||
win.removeMenu()
|
||||
//win.webContents.openDevTools()
|
||||
win.loadFile('index.html')
|
||||
win.on('closed', () => {
|
||||
win = null
|
||||
})
|
||||
|
||||
win.once('ready-to-show', () => {
|
||||
win.show()
|
||||
})
|
||||
}
|
||||
|
||||
app.on('ready', createWindow);
|
||||
|
||||
app.on('window-all-closed', () => {
|
||||
if (process.platform !== 'darwin') {
|
||||
app.quit()
|
||||
}
|
||||
});
|
||||
|
||||
app.on('activate', () => {
|
||||
if (win === null) {
|
||||
createWindow()
|
||||
}
|
||||
});
|
||||
2370
package-lock.json
generated
Normal file
2370
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
39
package.json
Normal file
39
package.json
Normal file
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"name": "youtube-dl-gui",
|
||||
"version": "1.0.0",
|
||||
"description": "A GUI for the YouTube-dl library",
|
||||
"main": "main.js",
|
||||
"scripts": {
|
||||
"start": "electron .",
|
||||
"dist": "electron-builder"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Jelle Glebbeek",
|
||||
"license": "MIT",
|
||||
"devDependencies": {
|
||||
"electron": "^8.2.0",
|
||||
"electron-builder": "^22.4.1"
|
||||
},
|
||||
"dependencies": {
|
||||
"@fortawesome/fontawesome-free": "^5.13.0",
|
||||
"bootstrap-material-design": "^4.1.2",
|
||||
"custom-electron-titlebar": "^3.2.2-hotfix62",
|
||||
"fs": "0.0.1-security",
|
||||
"jquery": "^3.4.1",
|
||||
"popper.js": "^1.16.1",
|
||||
"youtube-dl": "^3.0.2"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "yt-dl-gui"
|
||||
},
|
||||
"build": {
|
||||
"appId": "com.jelleglebbeek.youtube-dl-gui",
|
||||
"productName": "YouTube Downloader GUI",
|
||||
"copyright": "Copyright © 2020 Jelle Glebbeek",
|
||||
"win": {
|
||||
"target": "portable",
|
||||
"icon": "build/icon.ico"
|
||||
}
|
||||
}
|
||||
}
|
||||
36
plugins.js
Normal file
36
plugins.js
Normal file
@@ -0,0 +1,36 @@
|
||||
window.$ = window.jQuery = require('jquery')
|
||||
const customTitlebar = require('custom-electron-titlebar')
|
||||
|
||||
let stepper
|
||||
|
||||
new customTitlebar.Titlebar({
|
||||
backgroundColor: customTitlebar.Color.fromHex('#000000'),
|
||||
maximizable: false,
|
||||
shadow: true,
|
||||
titleHorizontalAlignment: "left",
|
||||
enableMnemonics: false,
|
||||
icon: "bin/icon-light.png"
|
||||
})
|
||||
|
||||
$(document).ready(function () {
|
||||
stepper = new Stepper($('.bs-stepper')[0], {
|
||||
linear: true,
|
||||
animation: true
|
||||
})
|
||||
})
|
||||
|
||||
function next() {
|
||||
stepper.next()
|
||||
}
|
||||
|
||||
function setType(type) {
|
||||
if(type === "audio") {
|
||||
$('#quality').empty().append(new Option("Best", "best")).append(new Option("Worst", "worst")).prop("disabled", false).val("best")
|
||||
} else if(type === "video") {
|
||||
$('#quality').empty()
|
||||
availableFormats.forEach(function(quality) {
|
||||
$('#quality').append(new Option(quality, quality.slice(0,-1))).prop("disabled", false)
|
||||
})
|
||||
$('#quality').val(availableFormats[availableFormats.length-1].slice(0,-1))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user