654 lines
28 KiB
JavaScript
654 lines
28 KiB
JavaScript
// Global socketio variable for socketio operations
|
||
var socket = "";
|
||
// yt-dlp-web-api url
|
||
var apiurl = "localhost:8888";
|
||
// Notification count
|
||
var notifications = 0;
|
||
// Web page title, used to help with notifications
|
||
var title = "yt-dlp web ui";
|
||
// Global videojs player variable for videojs operations
|
||
var player = "";
|
||
// Sources for video being clipped
|
||
var vsources = "";
|
||
// All the easy id3 fields to add to the easy id3 form
|
||
var eid3fields = ["album", "bpm", "compilation", "composer", "encodedby", "lyricist", "length", "media", "mood", "title", "version", "artist", "albumartist", "conductor", "arranger", "discnumber", "tracknumber", "author", "albumartistsort", "albumsort", "composersort", "artistsort", "titlesort", "language", "genre", "date", "performer", "asin", "performer", "catalognumber"];
|
||
// Info for current video
|
||
var ginfo = [];
|
||
// Limits of the server
|
||
var glimits = [];
|
||
var streamPick = "";
|
||
// Predefined themes
|
||
var themes = {
|
||
"white": {
|
||
"bgcolor": "#ffffff",
|
||
"textcolor": "#000000"
|
||
},
|
||
"yellow": {
|
||
"bgcolor": "#ffff00",
|
||
"textcolor": "#000000"
|
||
},
|
||
"purple": {
|
||
"bgcolor": "#b642f5",
|
||
"textcolor": "#ffffff"
|
||
},
|
||
"grayblue": {
|
||
"bgcolor": "#636987",
|
||
"textcolor": "#ffffff"
|
||
},
|
||
"sky": {
|
||
"bgcolor": "#aaaaff",
|
||
"textcolor": "#ffffff"
|
||
},
|
||
"black": {
|
||
"bgcolor": "#000000",
|
||
"textcolor": "#ffffff"
|
||
}
|
||
};
|
||
const DO_NOT_RETRY = "coi-dnr";
|
||
window.coi = {
|
||
shouldRegister: () => {
|
||
if (!window["sessionStorage"]) return false;
|
||
let register = !window.sessionStorage.getItem(DO_NOT_RETRY);
|
||
window.sessionStorage.removeItem(DO_NOT_RETRY);
|
||
return register;
|
||
},
|
||
coepCredentialless: () => {
|
||
if (window["chrome"] !== undefined || window["netscape"] !== undefined) return true;
|
||
const test = /firefox(\/(\d+))?/i;
|
||
try {
|
||
const match = test.exec(window.navigator.userAgent);
|
||
if (match && match[2]) {
|
||
const n = parseInt(match[2]);
|
||
if (!isNaN(n) && n >= 119) return true;
|
||
}
|
||
} catch (e) { }
|
||
return false;
|
||
},
|
||
doReload: () => {
|
||
window.sessionStorage.setItem(DO_NOT_RETRY, "true");
|
||
window.location.reload()
|
||
}
|
||
};
|
||
// Do when the document has loaded
|
||
document.addEventListener("DOMContentLoaded", function () {
|
||
// Check url for youtube link and if there's a youtube link prefill the Video/Playlist URL field with it
|
||
var here = window.location.href;
|
||
if (here.includes("?h")) {
|
||
document.querySelector("#url").value = "https://youtube.com/watch?v=" + here.split("?v=")[1];
|
||
}
|
||
// Load theme from last theme settings
|
||
// If a gradient is set up load the gradient
|
||
if (document.querySelector("#gradienttop").value != "none") {
|
||
if (document.querySelector("#gradientbottom").value != "none") {
|
||
setTheme(true);
|
||
} else {
|
||
setTheme();
|
||
}
|
||
} else {
|
||
setTheme();
|
||
}
|
||
// Connect to the socketio server
|
||
socket = io(apiurl);
|
||
// Emit signal to query the limits of the server
|
||
socket.emit("limits", {});
|
||
// Preset the step variable to 1, this makes sure step based functionality won't break
|
||
document.querySelector("#step").value = 1;
|
||
// Hide all warnings, in some cases this is unncessary, but better safe than sorry
|
||
document.querySelector("#clipperWarning").style.display = "none";
|
||
document.querySelector("#clipperWarning2").style.display = "none";
|
||
document.querySelector("#clipperWarning3").style.display = "none";
|
||
document.querySelector("#clipperWarning4").style.display = "none";
|
||
document.querySelector('#urlWarning').style.display = 'none';
|
||
// Populate easy id3 configuration form with fields from the eid3fields variable
|
||
for (it in eid3fields) {
|
||
document.querySelector("#id3").innerHTML += '<label for="' + eid3fields[it] + '">' + eid3fields[it] + '</label>';
|
||
document.querySelector("#id3").innerHTML += '<input type="text" id="' + eid3fields[it] + '" />';
|
||
}
|
||
// Adds submit button to easy id3 configuration form
|
||
document.querySelector("#id3").innerHTML += '<br><button onclick="return id3submit()">Submit</button><br>';
|
||
// All emissions return here
|
||
socket.on("done", (data) => {
|
||
// If necessary add a notification to the tab of the site
|
||
notify();
|
||
// If there's an error in the result just make the row with the given data, logic for what to actually show is in makeRow
|
||
if (data["error"]) {
|
||
var tr = makeRow(data);
|
||
document.querySelector("#results").prepend(tr);
|
||
} else if (data["method"] == "limits") {
|
||
// For the limits method we don't want to show snything in the results table, but the limits table at the top of the page
|
||
var limits = data["limits"];
|
||
glimits = limits;
|
||
// Populate the limits table
|
||
for (it in limits) {
|
||
var lv = Number(limits[it]["limitvalue"]);
|
||
var curl = "";
|
||
if (limits[it]["limitid"].includes("Length")) {
|
||
curl = "" + (Math.floor(lv / 60)).toString().padStart(2, "0") + ":" + (lv % 60).toString().padStart(2, "0");
|
||
} else {
|
||
curl = lv.toString();
|
||
}
|
||
document.querySelector("#" + limits[it]["limitid"]).innerHTML = curl;
|
||
}
|
||
} else {
|
||
// Get method
|
||
var method = data["method"];
|
||
// If done with step 1 of subtitles increment to step 2, otherwise reset to step 1
|
||
if (method == "subtitles") {
|
||
if ("step" in data) {
|
||
document.querySelector("#step").value = 2;
|
||
} else {
|
||
document.querySelector("#step").value = 1;
|
||
}
|
||
}
|
||
// When done with clipping reset to step 1
|
||
if (method == "clip") {
|
||
document.querySelector("#step").value = 1;
|
||
}
|
||
// When done wih getting streams set global video info
|
||
if (method == "streams") {
|
||
ginfo = data["info"];
|
||
}
|
||
// When done with step 1 of clipping (getting the tracks), populate the sources for the videojs player and reload it
|
||
if (method == "getClipperTracks") {
|
||
var sources = [];
|
||
for (i in data["info"]["formats"]) {
|
||
var cob = data["info"]["formats"][i];
|
||
if (cob["vcodec"] != "none") {
|
||
var codec = cob["video_ext"];
|
||
sources.push({
|
||
"type": "video/" + codec,
|
||
"src": cob["url"],
|
||
"resolution": cob["resolution"],
|
||
"format_id": cob["format_id"]
|
||
});
|
||
}
|
||
}
|
||
loadVideo(sources);
|
||
} else {
|
||
// For any other case just make a row with the given data
|
||
var tr = makeRow(data);
|
||
document.querySelector("#results").prepend(tr);
|
||
}
|
||
}
|
||
// Remove the loading spinner if it exists
|
||
if (data["method"] != "limits") {
|
||
document.querySelector("[id='" + data["spinnerid"] + "']").remove();
|
||
}
|
||
});
|
||
// When the tab is viewed the notifications are reset to 0 and the notifications are removed from the tab title
|
||
document.addEventListener("visibilitychange", () => {
|
||
if (!document.hidden) {
|
||
document.title = title;
|
||
notifications = 0;
|
||
}
|
||
});
|
||
});
|
||
|
||
// When downloading a stream from the select the Get all download links for video method provides just open the link in a new tab
|
||
function dlStream(sid) {
|
||
if (document.getElementById("combineCheck").checked == true) {
|
||
//var aurl = ginfo["formats"][document.querySelector("#a_" + sid).value]["url"];
|
||
//var vurl = ginfo["formats"][document.querySelector("#v_" + sid).value]["url"];
|
||
//combineStreams(aurl, vurl);
|
||
var vid = ginfo["formats"][document.querySelector("#v_" + sid).value]["format_id"];
|
||
var aid = ginfo["formats"][document.querySelector("#a_" + sid).value]["format_id"];
|
||
combineStreamsHard(vid, aid);
|
||
} else {
|
||
window.open(ginfo["formats"][streamPick]["url"], "_blank");
|
||
}
|
||
}
|
||
|
||
const { fetchFile } = FFmpegUtil;
|
||
const { FFmpeg } = FFmpegWASM;
|
||
let ffmpeg = null;
|
||
|
||
// On hold, ffmpeg-wasm needs more testing
|
||
// import { FFmpeg } from '@ffmpeg/ffmpeg';
|
||
// import { fetchFile, toBlobURL } from '@ffmpeg/util';
|
||
async function combineStreams(aurl, vurl, sid) {
|
||
console.log("loading ffmpeg");
|
||
try {
|
||
if (ffmpeg === null) {
|
||
ffmpeg = new FFmpeg();
|
||
ffmpeg.on("log", ({ message }) => {
|
||
console.log(message);
|
||
})
|
||
ffmpeg.on("progress", ({ progress, time }) => {
|
||
console.log(`${progress * 100} %, time: ${time / 1000000} s`);
|
||
});
|
||
// TODO: Providing necessary headers
|
||
// TODO: Figure out what needs to be done to get this working live
|
||
await ffmpeg.load({
|
||
coreURL: '/node_modules/@ffmpeg/core/dist/umd/ffmpeg-core.js',
|
||
//wasmURL: '/node_modules/@ffmpeg/core-mt/dist/umd/ffmpeg-core.wasm',
|
||
//workerURL: '/node_modules/@ffmpeg/core-mt/dist/umd/ffmpeg-core.worker.js',
|
||
});
|
||
}
|
||
console.log("loaded ffmpeg");
|
||
console.log("fetching audio");
|
||
await ffmpeg.writeFile('inputaudio.mp3', await fetchFile(aurl));
|
||
console.log("fetching video");
|
||
console.log(vurl);
|
||
await ffmpeg.writeFile('inputvideo.mp4', await fetchFile(vurl));
|
||
// Todo: codec matching for more efficient mixing
|
||
console.log("mixing");
|
||
await ffmpeg.exec(['-i', 'inputvideo.mp4', '-i', 'inputaudio.mp3', '-c:v', 'mp4', '-c:a', 'aac', 'output.mp4']);
|
||
console.log("done");
|
||
const data = await ffmpeg.readFile('output.mp4');
|
||
window.open(URL.createObjectURL(new Blob([data.buffer], { type: 'video/mp4' })), "_blank");
|
||
} catch (e){
|
||
console.trace();
|
||
}
|
||
}
|
||
|
||
function combineStreamsHard(format_id, format_id_audio){
|
||
var spinnerid = genSpinner();
|
||
var data = {
|
||
"url": ginfo["original_url"],
|
||
"format_id": format_id,
|
||
"format_id_audio": format_id_audio,
|
||
"spinnerid": spinnerid
|
||
}
|
||
socket.emit("combine", data);
|
||
}
|
||
|
||
// Method for showing notifications in title
|
||
function notify() {
|
||
notifications += 1;
|
||
document.title = "(" + notifications.toString() + ") " + title;
|
||
}
|
||
|
||
// Very important, the logic here decides what is returned to the user via the results tables
|
||
function makeRow(data) {
|
||
// Create a table row element
|
||
var tr = document.createElement("tr");
|
||
// Create variable to store generated html for later adding to the tr element
|
||
var lhtml = "";
|
||
// Columns of the table
|
||
const cols = ["status", "title", "link", "details"];
|
||
// Iterate through each column of the table and construct and add the html for each column
|
||
for (col in cols) {
|
||
col = cols[col];
|
||
// If the column is in the data or it's status then...
|
||
if ((col in data) || (col == "status")) {
|
||
// Starts the column html
|
||
lhtml += "<td>";
|
||
// If the column is status, display either an error indicator or a success indicator
|
||
if (col == "status") {
|
||
if (data["error"]) {
|
||
lhtml += "<button class='button error'>Error</button>";
|
||
} else {
|
||
lhtml += "<button class='button success'>Success</button></td>";
|
||
}
|
||
} else if (col == "link") {
|
||
// If the column is link then wrap the link in the requisite html to be clickable
|
||
lhtml += "<a href='" + data[col] + "'>" + data[col] + "</a>";
|
||
} else if (col == "details") {
|
||
// If the column is details first display whatever data has been set for this column
|
||
lhtml += "<p>" + data[col] + "</p>";
|
||
// If select is in our data keys...
|
||
if ("select" in data) {
|
||
// Generate specially formatted selects based on whether this is for streams or another method (subtitles)
|
||
if (data["method"] == "streams") {
|
||
// Used to differentiate selects when there are multiple
|
||
var iid = crypto.randomUUID();
|
||
ahtml = "<br>Audio<select onchange='setStreamPick(this.id)' id='a_" + iid + "'>";
|
||
vhtml = "Video<select onchange='setStreamPick(this.id)' id='v_" + iid + "'>";
|
||
chtml = "Both<select onchange='setStreamPick(this.id)' id='c_" + iid + "'>";
|
||
ohtml = "Misc<select onchange='setStreamPick(this.id)' id='m_" + iid + "'>";
|
||
for (it in ginfo["formats"]) {
|
||
if (ginfo["formats"][it]["audio_ext"] == "none") {
|
||
if (ginfo["formats"][it]["video_ext"] == "none") {
|
||
ohtml += "<option value='" + it.toString() + "'>" + ginfo["formats"][it]["format"] + "</option>";
|
||
} else {
|
||
vhtml += "<option value='" + it.toString() + "'>" + ginfo["formats"][it]["format"] + " / " + ginfo["formats"][it]["video_ext"] + "</option>";
|
||
}
|
||
} else if (ginfo["formats"][it]["video_ext"] == "none") {
|
||
ahtml += "<option value='" + it.toString() + "'>" + ginfo["formats"][it]["format"] + " / " + ginfo["formats"][it]["audio_ext"] + "</option>";
|
||
} else {
|
||
chtml += "<option value='" + it.toString() + "'>" + ginfo["formats"][it]["format"] + " / " + ginfo["formats"][it]["ext"] + "</option>";
|
||
}
|
||
}
|
||
lhtml += ahtml + "</select>" + vhtml + "</select>" + chtml + "</select>" + ohtml + "</select>";
|
||
// TODO: Split dropdown into audio/video/both dropdowns Done
|
||
// TODO: Combine and download button for audio/video edit: checkbox
|
||
lhtml += "<br>" + `<label class="control checkbox">
|
||
<input type="checkbox" name="combineCheck" id="combineCheck" />
|
||
<span class="control-indicator"></span>
|
||
<span class="control-label">Combine audio and video selections?</span>
|
||
</label>`;
|
||
lhtml += "<br><button onclick='dlStream(`" + iid + "`)'>Download selected</button>";
|
||
} else {
|
||
var iid = crypto.randomUUID();
|
||
lhtml += "<select data-iid='" + iid + "' id='langSelect'>";
|
||
for (it in data["select"]) {
|
||
lhtml += "<option value='" + data["select"][it] + "'>" + data["select"][it] + "</option>";
|
||
}
|
||
lhtml += "</select>";
|
||
lhtml += '<input id="autoSubs" data-iid="' + iid + '" type="checkbox" name="checkbox" />';
|
||
lhtml += '<label for="autoSubs">Download automatic subs?</label>';
|
||
lhtml += '<br><button data-iid="' + iid + '" onclick="process(this)">Get subtitles</button>';
|
||
}
|
||
}
|
||
} else {
|
||
// Anything else just display the data directly
|
||
lhtml += "<p>" + data[col] + "</p>";
|
||
}
|
||
// Close out the column
|
||
lhtml += "</td>";
|
||
} else {
|
||
// Otherwise write the row with nothing in it so all the other rows display properly
|
||
lhtml += "<td></td>"
|
||
}
|
||
}
|
||
// Add html to tr element and return it for display
|
||
tr.innerHTML = lhtml;
|
||
return tr;
|
||
}
|
||
|
||
// Generate a turretcss spinner with a random id (so it can be removed later) and append it to the spinners div (just above the results table)
|
||
function genSpinner() {
|
||
var spinnerid = crypto.randomUUID();
|
||
var sp = document.createElement("button");
|
||
sp.setAttribute("id", spinnerid);
|
||
sp.setAttribute("class", "spinner");
|
||
sp.setAttribute("title", "Loading");
|
||
document.querySelector("#spinners").append(sp);
|
||
return spinnerid;
|
||
}
|
||
|
||
// When an ID3 submission is made we use a submit button, this way we run the code we need and return false, meaning that the form won't actually post
|
||
// Constructs an object with all the id3 form values to use with toMP3, which will in turn set these values to be added to the metadata of the MP3
|
||
function id3submit() {
|
||
var form = document.getElementById('id3');
|
||
var values = {};
|
||
for (var it in form.elements) {
|
||
values[form.elements[it].id] = form.elements[it].value;
|
||
}
|
||
var url = document.querySelector('#url').value;
|
||
toMP3(url, values);
|
||
return false;
|
||
}
|
||
|
||
// Hide/show the ID3 form
|
||
function toggleID3Form() {
|
||
var form = document.querySelector("#id3");
|
||
var button = document.querySelector("#id3toggle");
|
||
if (form.style.display == "none") {
|
||
form.style.display = "block";
|
||
button.textContent = "Hide ID3 form";
|
||
} else {
|
||
form.style.display = "none";
|
||
button.textContent = "Show ID3 form";
|
||
}
|
||
}
|
||
|
||
// Generate spinner and emit toMP3 with the spinnerid (to remove the spinner later) to download mp3 and optionally add metadata to the file
|
||
function toMP3(url, id3data = null) {
|
||
var spinnerid = genSpinner();
|
||
socket.emit("toMP3", { "url": url, "spinnerid": spinnerid, "id3": id3data });
|
||
}
|
||
|
||
// Emit playlist to download a playlist as MP3s and zip them for download
|
||
function playlist(url) {
|
||
var spinnerid = genSpinner();
|
||
socket.emit("playlist", { "url": url, "spinnerid": spinnerid });
|
||
}
|
||
|
||
// Emit either step of subtitles
|
||
// Step 1 will give us a list of the subtitle languages
|
||
// Step 2 will give us a download of the selected subtitle
|
||
function subtitles(url, iid = null) {
|
||
var spinnerid = genSpinner();
|
||
var step = Number(document.querySelector("#step").value);
|
||
var data = { "url": url, "spinnerid": spinnerid, "step": step }
|
||
if (step == 2) {
|
||
data["languageCode"] = document.querySelector('[data-iid="' + iid + '"]#langSelect').value;
|
||
data["autoSub"] = document.querySelector('[data-iid="' + iid + '"]#autoSubs').checked;
|
||
}
|
||
socket.emit("subtitles", data);
|
||
}
|
||
|
||
// Generic getInfo method to get all the info for a given link
|
||
function getInfo(url, method) {
|
||
var spinnerid = genSpinner()
|
||
var data = { "url": url, "spinnerid": spinnerid, "method": method };
|
||
socket.emit("getInfoEvent", data);
|
||
}
|
||
|
||
// Clip method, ultimately produces a clip of a video or a gif of that clip
|
||
function clip(url, directURL = null, gif = null, format_id = null, resolution = null) {
|
||
// Get the start and end times for the clip
|
||
var timeA = document.querySelector("#timeA").value;
|
||
var timeB = document.querySelector("#timeB").value;
|
||
// If there is a set resolution, check to make sure that resolution is not larger then the servers limit
|
||
// Otherwise display an error message
|
||
if (resolution != null) {
|
||
if (resolution > glimits[4]["limitvalue"]) {
|
||
document.querySelector("#clipperWarning4").style.display = "block";
|
||
}
|
||
} else if (gif && ((timeB - timeA) > glimits[3]["limitvalue"])) {
|
||
// If this is a gif check if the length of the clip is longer than the servers limit
|
||
// If so, display an error message
|
||
document.querySelector("#clipperWarning3").style.display = "block";
|
||
} else {
|
||
// If the start of the clip is after the end of the clip display an error message
|
||
if (Number(timeA) > Number(timeB)) {
|
||
document.querySelector("#clipperWarning2").style.display = "block";
|
||
} else {
|
||
var spinnerid = genSpinner();
|
||
// Set up the required values for the clip method and then set up additional values if they are not null
|
||
var data = { "url": url, "spinnerid": spinnerid, "timeA": timeA, "timeB": timeB };
|
||
if (format_id != null) {
|
||
data["format_id"] = format_id
|
||
}
|
||
if (directURL != null) {
|
||
data["directURL"] = directURL
|
||
}
|
||
if (gif != null) {
|
||
data["gif"] = true;
|
||
}
|
||
socket.emit("clip", data);
|
||
}
|
||
}
|
||
}
|
||
|
||
// This loads a video into videojs with multiple sources and generates a list of options in a select for the user to choose a stream
|
||
// setSource handles actually setting the stream
|
||
function loadVideo(sources) {
|
||
// Clear the video container, to be populated later
|
||
document.querySelector("#videoContainer").innerHTML = "";
|
||
// Create video element to be used to create videojs player
|
||
var video = document.createElement("video");
|
||
// Sources object for videojs initialization
|
||
var sdata = {
|
||
"sources": sources
|
||
};
|
||
// Set global sources for the video
|
||
vsources = sources;
|
||
// Set some basic attributes for the videojs player
|
||
video.setAttribute("id", "clipPlayer");
|
||
video.setAttribute("class", "video-js vjs-default-skin");
|
||
video.setAttribute("controls", "");
|
||
video.setAttribute("width", "720");
|
||
// Add the video to the DOM
|
||
document.querySelector("#videoContainer").append(video);
|
||
// Clear the source selector
|
||
document.querySelector("#srcSelect").innerHTML = "";
|
||
// Populate source selector
|
||
for (it in sources) {
|
||
document.querySelector("#srcSelect").innerHTML += "<option value='" + it + "'>" + sources[it]["resolution"] + " " + sources[it]["type"] + "</option>";
|
||
}
|
||
// If the videojs player object isn't empty then dispose it
|
||
if (player != "") {
|
||
player.dispose();
|
||
}
|
||
// Test if this is doing anything
|
||
//document.querySelector("#clipPlayer").innerHTML = "";
|
||
// Initialize the videojs player
|
||
player = videojs('clipPlayer', sdata, function onPlayerReady() {
|
||
this.on("loadedmetadata", function () {
|
||
// If the video is too long display a warning
|
||
if (this.duration() < glimits[0]["limitvalue"]) {
|
||
document.querySelector("#timeA").max = this.duration();
|
||
document.querySelector("#timeB").max = this.duration();
|
||
document.querySelector("#clipper").style.display = "block";
|
||
} else {
|
||
document.querySelector("#clipper").innerHTML = "";
|
||
document.querySelector("#clipperWarning").style.display = "block";
|
||
}
|
||
});
|
||
});
|
||
}
|
||
|
||
// Set the current source of the videojs player based on the selected source
|
||
function setSource(srcIndex) {
|
||
player.src(vsources[Number(srcIndex)]);
|
||
}
|
||
|
||
// Set the current time of the video player when the time sliders change, format the time display to minutes and seconds
|
||
function updatePlayerTime(range) {
|
||
player.currentTime(range.value);
|
||
if (range.id == "timeA") {
|
||
document.querySelector("#timeStart").value = (Math.floor(range.value / 60)).toString().padStart(2, "0") + ":" + (range.value % 60).toString().padStart(2, "0");
|
||
} else if (range.id == "timeB") {
|
||
document.querySelector("#timeEnd").value = (Math.floor(range.value / 60)).toString().padStart(2, "0") + ":" + (range.value % 60).toString().padStart(2, "0");
|
||
}
|
||
}
|
||
|
||
// Submit the clip to be cut
|
||
function clipSecond() {
|
||
document.querySelector("#step").value = 2;
|
||
process();
|
||
}
|
||
|
||
// Semi generic method tying buttons to emissions
|
||
// This should be reworked and the extraSteps stuff should be removed completely
|
||
// Recommendation: just a set of if elses for the exact method that was passed
|
||
function process(button = null, extraSteps = null) {
|
||
// Get method from method select
|
||
var method = document.querySelector('#method').value;
|
||
// Get the url from the Video/Playlist URL field
|
||
var url = document.querySelector('#url').value;
|
||
// Check if the url is valid
|
||
if (isValidUrl(url)) {
|
||
// Hide warnings until needed
|
||
document.querySelector('#urlWarning').style.display = 'none';
|
||
document.querySelector("#clipperWarning").style.display = "none";
|
||
// Run toMP3 function
|
||
if (method == "toMP3") {
|
||
toMP3(url);
|
||
} else if (method == "playlist") {
|
||
// Run playlist function
|
||
playlist(url);
|
||
} else if (method == "subtitles") {
|
||
// Run subtitles function
|
||
// If a button has been passed (in order to identify the select) pass the iid to subtitles for selected subtitles retrieval
|
||
if (button == null) {
|
||
subtitles(url);
|
||
} else {
|
||
subtitles(url, iid = button.getAttribute('data-iid'));
|
||
}
|
||
} else if (method == "streams") {
|
||
// Run getInfo function with the streams method
|
||
getInfo(url, "streams");
|
||
} else if (method == "clip") {
|
||
// Get the step of clipping we're at
|
||
var step = Number(document.querySelector("#step").value);
|
||
// Add extraSteps if specified
|
||
if (extraSteps != null) {
|
||
extraSteps = Number(extraSteps);
|
||
step += extraSteps;
|
||
}
|
||
// If we're at step 1 get a list of tracks for the video
|
||
if (step == 1) {
|
||
getInfo(url, "getClipperTracks");
|
||
} else if (step == 2) {
|
||
// If we're at step 2 get some info and clip the video
|
||
var format = vsources[Number(document.querySelector("#srcSelect").value)]["format_id"]
|
||
var format_id = format["format_id"];
|
||
var resolution = format["width"]
|
||
if (document.querySelector("#toGif").checked) {
|
||
clip(url, null, true, format_id = format_id, resolution = resolution);
|
||
} else {
|
||
clip(url, null, null, format_id = format_id, resolution = resolution);
|
||
}
|
||
}
|
||
}
|
||
} else {
|
||
// If the url isn't valid display a warning
|
||
document.querySelector('#urlWarning').style.display = 'block';
|
||
}
|
||
}
|
||
|
||
// Checks if an entered url is valid
|
||
// https://www.freecodecamp.org/news/check-if-a-javascript-string-is-a-url/
|
||
const isValidUrl = urlString => {
|
||
try {
|
||
return Boolean(new URL(urlString));
|
||
}
|
||
catch (e) {
|
||
return false;
|
||
}
|
||
}
|
||
|
||
// Get a random integer in an inclusive range
|
||
function randomInt(min, max) {
|
||
return Math.floor(Math.random() * (max - min + 1) + min)
|
||
}
|
||
|
||
// Set theme based on the selected theme or custom values
|
||
function setTheme(customgradient = false) {
|
||
var r = document.querySelector(':root');
|
||
var theme = document.querySelector("#theme").value;
|
||
if (theme == "custom") {
|
||
theme = {
|
||
"bgcolor": document.querySelector("#bgcolor").value,
|
||
"textcolor": document.querySelector("#textcolor").value
|
||
}
|
||
if (customgradient) {
|
||
// Set the gradient with the custom values from Top/Bottom color of gradient
|
||
theme["gradienttop"] = document.querySelector("#gradienttop").value;
|
||
theme["gradientbottom"] = document.querySelector("#gradientbottom").value;
|
||
} else {
|
||
// Set the gradient to a solid color
|
||
theme["gradienttop"] = theme["bgcolor"];
|
||
theme["gradientbottom"] = theme["bgcolor"];
|
||
}
|
||
} else {
|
||
var themename = theme;
|
||
theme = themes[theme];
|
||
if (themename != "sky") {
|
||
theme["gradienttop"] = theme["bgcolor"];
|
||
theme["gradientbottom"] = theme["bgcolor"];
|
||
} else {
|
||
// Generate two light blues colors in hex format and set the top and bottom of the gradient to these
|
||
var top0 = randomInt(50, 99).toString().padStart(2, "0");
|
||
top0 = "#" + top0 + top0 + "ff";
|
||
var bottom0 = randomInt(50, 99).toString().padStart(2, "0");
|
||
bottom0 = "#" + bottom0 + bottom0 + "ff";
|
||
theme["gradienttop"] = top0;
|
||
theme["gradientbottom"] = bottom0;
|
||
}
|
||
}
|
||
for (it in theme) {
|
||
r.style.setProperty('--' + it, theme[it]);
|
||
}
|
||
var rs = getComputedStyle(r);
|
||
}
|
||
|
||
|
||
// Generic toggle method, requires an element with an id and a element that will be used for toggling the visibility of the main element with an id of the main element + _toggle
|
||
function toggle(id) {
|
||
if (document.querySelector(id).style.display == "none") {
|
||
document.querySelector(id).style.display = "block";
|
||
document.querySelector(id + "_toggle").innerHTML = "➖ Hide";
|
||
} else {
|
||
document.querySelector(id).style.display = "none";
|
||
document.querySelector(id + "_toggle").innerHTML = "➕ Show";
|
||
}
|
||
}
|
||
|
||
function setStreamPick(sid) {
|
||
streamPick = Number(document.getElementById(sid).value);
|
||
} |