Initial srt support setup; ntfy bind change, seek event alterations; js-cookie file upgraded

This commit is contained in:
itdominator 2025-01-31 23:24:30 -06:00
parent 5ef1259bb4
commit 5f9dfa12f4
12 changed files with 272 additions and 154 deletions

View File

@ -96,7 +96,7 @@ def _start_rtsp_and_ntfy_server():
@daemon_threaded @daemon_threaded
def _start_ntfy_server_threaded(): def _start_ntfy_server_threaded():
os.chdir(NTFY_PATH) os.chdir(NTFY_PATH)
command = ["./ntfy", "serve", "--behind-proxy", "--listen-http", ":7777"] command = ["./ntfy", "serve", "--behind-proxy", "--listen-http", "127.0.0.1:7777"]
process = subprocess.Popen(command) process = subprocess.Popen(command)
process.wait() process.wait()

View File

@ -4,6 +4,7 @@ import requests
import uuid import uuid
# Lib imports # Lib imports
from flask import abort
from flask import make_response from flask import make_response
from flask import redirect from flask import redirect
from flask import request from flask import request
@ -42,6 +43,17 @@ def home():
message = 'Must use GET request type...') message = 'Must use GET request type...')
# NOTE: Yeah, not exactly logged but meh.
@app.route('/log-client-exception', methods=['GET', 'POST'])
def ui_failure_exception_tracker():
if request.method == 'POST':
DATA = str(request.values['exception_data']).strip()
print(f"\n\n{DATA}")
return json_message.create("success", "UI Exception logged...")
return json_message.create("danger", "Must use POST request type...")
@app.route('/api/list-files/<_hash>', methods=['GET', 'POST']) @app.route('/api/list-files/<_hash>', methods=['GET', 'POST'])
def list_files(_hash = None): def list_files(_hash = None):
if request.method == 'POST': if request.method == 'POST':
@ -93,7 +105,12 @@ def file_manager_action(_type, _hash = None):
folder = view.get_current_directory() folder = view.get_current_directory()
file = view.get_path_part_from_hash(_hash) file = view.get_path_part_from_hash(_hash)
fpath = None;
try:
fpath = os.path.join(folder, file) fpath = os.path.join(folder, file)
except Exception as e:
return abort(404)
logger.debug(fpath) logger.debug(fpath)
if _type == "files": if _type == "files":

View File

@ -46,6 +46,15 @@
margin: 0 auto; margin: 0 auto;
} }
#selectedFile,
#video-controls {
font-size: x-large;
}
#seek-slider {
width: -moz-available !important;
}
/* CLASSES */ /* CLASSES */
.scroller { .scroller {

View File

Before

Width:  |  Height:  |  Size: 680 KiB

After

Width:  |  Height:  |  Size: 680 KiB

View File

@ -1,88 +1,76 @@
/*!
* JavaScript Cookie v2.2.0
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
;(function (factory) {
var registeredInModuleLoader;
if (typeof define === 'function' && define.amd) {
define(factory);
registeredInModuleLoader = true;
}
if (typeof exports === 'object') {
module.exports = factory();
registeredInModuleLoader = true;
}
if (!registeredInModuleLoader) {
var OldCookies = window.Cookies;
var api = window.Cookies = factory();
api.noConflict = function () {
window.Cookies = OldCookies;
return api;
};
}
}(function () {
function extend () {
var i = 0;
var result = {};
for (; i < arguments.length; i++) {
var attributes = arguments[ i ];
for (var key in attributes) {
result[key] = attributes[key];
}
}
return result;
}
function decode (s) { /*! js-cookie v3.0.4 | MIT */
return s.replace(/(%[0-9A-Z]{2})+/g, decodeURIComponent); ;
(function (global, factory) {
typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() :
typeof define === 'function' && define.amd ? define(factory) :
(global = typeof globalThis !== 'undefined' ? globalThis : global || self, (function () {
var current = global.Cookies;
var exports = global.Cookies = factory();
exports.noConflict = function () { global.Cookies = current; return exports; };
})());
})(this, (function () { 'use strict';
/* eslint-disable no-var */
function assign (target) {
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var key in source) {
target[key] = source[key];
} }
}
return target
}
/* eslint-enable no-var */
function init (converter) { /* eslint-disable no-var */
function api() {} var defaultConverter = {
read: function (value) {
if (value[0] === '"') {
value = value.slice(1, -1);
}
return value.replace(/(%[\dA-F]{2})+/gi, decodeURIComponent)
},
write: function (value) {
return encodeURIComponent(value).replace(
/%(2[346BF]|3[AC-F]|40|5[BDE]|60|7[BCD])/g,
decodeURIComponent
)
}
};
/* eslint-enable no-var */
function set (key, value, attributes) { /* eslint-disable no-var */
function init (converter, defaultAttributes) {
function set (name, value, attributes) {
if (typeof document === 'undefined') { if (typeof document === 'undefined') {
return; return
} }
attributes = extend({ attributes = assign({}, defaultAttributes, attributes);
path: '/'
}, api.defaults, attributes);
if (typeof attributes.expires === 'number') { if (typeof attributes.expires === 'number') {
attributes.expires = new Date(new Date() * 1 + attributes.expires * 864e+5); attributes.expires = new Date(Date.now() + attributes.expires * 864e5);
}
if (attributes.expires) {
attributes.expires = attributes.expires.toUTCString();
} }
// We're using "expires" because "max-age" is not supported by IE name = encodeURIComponent(name)
attributes.expires = attributes.expires ? attributes.expires.toUTCString() : ''; .replace(/%(2[346B]|5E|60|7C)/g, decodeURIComponent)
.replace(/[()]/g, escape);
try {
var result = JSON.stringify(value);
if (/^[\{\[]/.test(result)) {
value = result;
}
} catch (e) {}
value = converter.write ?
converter.write(value, key) :
encodeURIComponent(String(value))
.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
key = encodeURIComponent(String(key))
.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent)
.replace(/[\(\)]/g, escape);
var stringifiedAttributes = ''; var stringifiedAttributes = '';
for (var attributeName in attributes) { for (var attributeName in attributes) {
if (!attributes[attributeName]) { if (!attributes[attributeName]) {
continue; continue
} }
stringifiedAttributes += '; ' + attributeName; stringifiedAttributes += '; ' + attributeName;
if (attributes[attributeName] === true) { if (attributes[attributeName] === true) {
continue; continue
} }
// Considers RFC 6265 section 5.2: // Considers RFC 6265 section 5.2:
@ -95,69 +83,66 @@
stringifiedAttributes += '=' + attributes[attributeName].split(';')[0]; stringifiedAttributes += '=' + attributes[attributeName].split(';')[0];
} }
return (document.cookie = key + '=' + value + stringifiedAttributes); return (document.cookie =
name + '=' + converter.write(value, name) + stringifiedAttributes)
} }
function get (key, json) { function get (name) {
if (typeof document === 'undefined') { if (typeof document === 'undefined' || (arguments.length && !name)) {
return; return
} }
var jar = {};
// To prevent the for loop in the first place assign an empty array // To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. // in case there are no cookies at all.
var cookies = document.cookie ? document.cookie.split('; ') : []; var cookies = document.cookie ? document.cookie.split('; ') : [];
var i = 0; var jar = {};
for (var i = 0; i < cookies.length; i++) {
for (; i < cookies.length; i++) {
var parts = cookies[i].split('='); var parts = cookies[i].split('=');
var cookie = parts.slice(1).join('='); var value = parts.slice(1).join('=');
if (!json && cookie.charAt(0) === '"') {
cookie = cookie.slice(1, -1);
}
try { try {
var name = decode(parts[0]); var found = decodeURIComponent(parts[0]);
cookie = (converter.read || converter)(cookie, name) || jar[found] = converter.read(value, found);
decode(cookie);
if (json) { if (name === found) {
try { break
cookie = JSON.parse(cookie);
} catch (e) {}
}
jar[name] = cookie;
if (key === name) {
break;
} }
} catch (e) {} } catch (e) {}
} }
return key ? jar[key] : jar; return name ? jar[name] : jar
} }
api.set = set; return Object.create(
api.get = function (key) { {
return get(key, false /* read as raw */); set,
}; get,
api.getJSON = function (key) { remove: function (name, attributes) {
return get(key, true /* read as json */); set(
}; name,
api.remove = function (key, attributes) { '',
set(key, '', extend(attributes, { assign({}, attributes, {
expires: -1 expires: -1
})); })
}; );
},
withAttributes: function (attributes) {
return init(this.converter, assign({}, this.attributes, attributes))
},
withConverter: function (converter) {
return init(assign({}, this.converter, converter), this.attributes)
}
},
{
attributes: { value: Object.freeze(defaultAttributes) },
converter: { value: Object.freeze(converter) }
}
)
}
api.defaults = {}; var api = init(defaultConverter, { path: '/' });
/* eslint-enable no-var */
api.withConverter = init;
return api; return api;
}
return init(function () {});
})); }));

View File

@ -0,0 +1,2 @@
(function(){"use strict";class i{number;startTime;endTime;text;constructor(t,e,s,r){this.number=t,this.startTime=e,this.endTime=s,this.text=r}}function c(n){try{if(!n||n===" ")return!1;const t={number:parseInt(n.match(/^\d+/g)[0]),timing:{start:n.match(/(\d+:){2}\d+,\d+/g)[0].replace(",","."),end:n.match(/(\d+:){2}\d+,\d+/g)[1].replace(",",".")},text:n.split(/\r?\n/g).slice(2,n.split(/\r?\n/g).length).join(`
`)};return new i(t.number,a(t.timing.start),a(t.timing.end),t.text)}catch{return!1}}function a(n){let t=n.split(":"),e=0,s=1;for(;t.length>0;)e+=s*parseFloat(t.pop(),10),s*=60;return e}async function o(n,t="utf-8"){return(!t||t==="")&&(t="utf-8"),fetch(n).then(e=>e.arrayBuffer()).then(e=>new TextDecoder(t).decode(e))}class d{src;encoding;lang;kind;label;default;body;needsTransform;cues=[];constructor(t){this.src=t.src,this.encoding=t.dataset.encoding,this.lang=t.srclang,this.kind=t.kind,this.label=t.label,this.default=t.default,this.needsTransform=!this.src.toLowerCase().endsWith(".vtt")}async parse(){this.body=await o(this.src,this.encoding),this.cues=this.body.split(/\r?\n\r?\n/g).map(c).filter(Boolean)}}async function l(n){const t=[...n.querySelectorAll("track")].map(e=>new d(e));for(const e of t){if(!e.needsTransform)continue;await e.parse();const s=n.addTextTrack(e.kind,e.label,e.lang);e.cues.forEach(r=>s.addCue(new VTTCue(r.startTime,r.endTime,r.text))),e.default&&(s.mode="showing")}}document.addEventListener("DOMContentLoaded",()=>[...document.querySelectorAll("video")].forEach(l))})();

View File

@ -1,17 +1,41 @@
const img2TabElm = document.getElementById("img2Tab"); const img2TabElm = document.getElementById("img2Tab");
const ctxDownloadElm = document.getElementById("ctxDownload"); const ctxDownloadElm = document.getElementById("ctxDownload");
const txt2copy = document.getElementById("txt2copy");
const menu = document.querySelector(".menu"); const menu = document.querySelector(".menu");
let text2copy = "";
let menuVisible = false; let menuVisible = false;
let img2TabSrc = null; let img2TabSrc = null;
let active_card = null; let active_card = null;
const img2Tab = () => { const img2Tab = () => {
if (img2TabSrc !== null) { if (img2TabSrc !== null) {
window.open(img2TabSrc,'_blank'); window.open(img2TabSrc,'_blank');
} }
}; };
const text2Clipboard = () => {
try {
navigator.clipboard.writeText(text2copy);
} catch (err) {
console.error('Failed to copy: ', err);
}
}
const getSelectedText = () => {
let text = "";
if (typeof window.getSelection != "undefined") {
text = window.getSelection().toString();
} else if (typeof document.selection != "undefined" && document.selection.type == "Text") {
text = document.selection.createRange().text;
}
return text;
}
const toggleMenu = command => { const toggleMenu = command => {
menu.style.display = command === "show" ? "block" : "none"; menu.style.display = command === "show" ? "block" : "none";
menu.style.zIndex = "9999"; menu.style.zIndex = "9999";
@ -25,6 +49,13 @@ const setPosition = ({ top, left }) => {
}; };
document.body.addEventListener("mouseup", e => {
const data = getSelectedText();
if (e.which !== 3 && e.target.innerText !== "Copy") {
text2copy = data;
}
});
document.body.addEventListener("click", e => { document.body.addEventListener("click", e => {
if(menuVisible) toggleMenu("hide"); if(menuVisible) toggleMenu("hide");
}); });
@ -42,6 +73,7 @@ document.body.addEventListener("contextmenu", e => {
elm.getAttribute("ftype") === "image") elm.getAttribute("ftype") === "image")
)) ? "block" : "none"; )) ? "block" : "none";
img2TabElm.style.display = (elm.nodeName === "IMG") ? "block" : "none"; img2TabElm.style.display = (elm.nodeName === "IMG") ? "block" : "none";
txt2copy.style.display = (text2copy !== "") ? "block" : "none";
img2TabSrc = (elm.nodeName === "IMG") ? elm.src : null; img2TabSrc = (elm.nodeName === "IMG") ? elm.src : null;
while (elm.nodeName != "BODY") { while (elm.nodeName != "BODY") {

View File

@ -52,15 +52,16 @@ const scrollFilesToTop = () => {
const closeFile = async () => { const closeFile = async () => {
const trailerPlayer = document.getElementById("trailerPlayer") const trailerPlayer = document.getElementById("trailerPlayer")
let title = document.getElementById("selectedFile"); let title = document.getElementById("selectedFile");
let player = document.getElementById("video");
document.getElementById("video-container").style.display = "node"; document.getElementById("video-container").style.display = "node";
document.getElementById("image-viewer").style.display = "none"; document.getElementById("image-viewer").style.display = "none";
document.getElementById("text-viewer").style.display = "none"; document.getElementById("text-viewer").style.display = "none";
document.getElementById("pdf-viewer").style.display = "none"; document.getElementById("pdf-viewer").style.display = "none";
player.jPlayer("pause")
title.innerText = ""; title.innerText = "";
trailerPlayer.src = "#"; trailerPlayer.src = "#";
player.pause();
trailerPlayer.style.display = "none"; trailerPlayer.style.display = "none";
// FIXME: Yes, a wasted call every time there is no stream. // FIXME: Yes, a wasted call every time there is no stream.

View File

@ -4,16 +4,23 @@ let shouldPlay = null;
let controlsTimeout = null; let controlsTimeout = null;
let playListMode = false; let playListMode = false;
let videoPlaylist = []; let videoPlaylist = [];
let seekto = null;
const getTimeFormatted = (duration = null) => { const getTimeFormatted = (duration = null) => {
if (duration == null) { return "00:00"; } if (duration == null) { return "00:00:00"; }
const hours = (duration / 3600).toFixed(2).split(".")[0]; const hours = (duration / 3600).toFixed(2).split(".")[0];
const minutes = (duration / 60).toFixed(2).split(".")[0]; let _duration = (duration / 60).toFixed(2).split(".")[0]
const minutes = (_duration - (hours * 60)).toFixed(2).split(".")[0];
const time = (duration / 60).toFixed(2) const time = (duration / 60).toFixed(2)
const seconds = Math.floor( (time - Math.floor(time) ) * 60); const seconds = Math.floor( (time - Math.floor(time) ) * 60);
return hours + ":" + minutes + ":" + seconds; return padnum(hours) + " : " + padnum(minutes) + " : " + padnum(seconds);
}
const padnum = (value = 0) => {
return (value > 9) ? value : "0" + value;
} }
@ -115,9 +122,49 @@ const loadMediaToPlayer = (title = "", video_path = "") => {
const modal = new bootstrap.Modal(document.getElementById('file-view-modal'), { keyboard: false }); const modal = new bootstrap.Modal(document.getElementById('file-view-modal'), { keyboard: false });
const player = document.getElementById("video"); const player = document.getElementById("video");
player.src = video_path; player.src = video_path;
loadSubtitles(title);
modal.show(); modal.show();
} }
const insertSubtitle = (path = "", label = "English Track: NONE") => {
fetch(path).then((response) => {
if(response.status == 200) {
const player = document.getElementById("video");
let track = document.createElement("TRACK");
track.srclang = "en";
track.kind = "subtitles";
track.label = label;
track.src = path;
track.setAttribute("default", "");
player.appendChild(track);
}
}).catch(function(error) {
let subStr1 = 'There has been a problem with your fetch operation: ' + error.message;
msg = "[Error] Status Code: 000\n[Message] -->" + subStr1;
return {'message': { 'type': "error", 'text': msg} }
});
}
const loadSubtitles = (title = "") => {
const player = document.getElementById("video");
const subtitle_stub = title.split(".")[0];
const subs = [`${subtitle_stub}.vtt`, `${subtitle_stub}.en.vtt`, `${subtitle_stub}.srt`, `${subtitle_stub}.en.srt`];
clearChildNodes(player)
let k = 0;
for (var i = 0; i < subs.length; i++) {
getSHA256Hash(subs[i]).then((_hash) => {
const data = "empty=NULL";
subtitle_path = "api/file-manager-action/files/" + _hash;
insertSubtitle(subtitle_path, subs[k]);
k += 1;
});
}
}
@ -221,12 +268,32 @@ $( "#video").bind( "stalled", async function(eve) {
}); });
$( "#seek-slider").bind( "change", async function(eve) { $( "#seek-slider").bind( "change", async function(eve) {
const slider = eve.target; const video = document.getElementById("video");
let video = document.getElementById("video");
let seekto = video.duration * (slider.value / 100);
video.currentTime = seekto; video.currentTime = seekto;
}); });
$( "#seek-slider").bind( "mousemove", async function(eve) {
const slider = eve.target;
const video = document.getElementById("video");
const seek = document.getElementById("seek-time");
const rect = slider.getBoundingClientRect()
const offset = rect.right - rect.width;
const slider_val = Math.floor(
(
(eve.pageX - offset) / (rect.right - offset)
) * 100
)
seekto = video.duration * (slider_val / 100);
seek.innerText = getTimeFormatted(seekto);
});
$( "#seek-slider").bind( "mouseleave", async function(eve) {
const seekto = document.getElementById("seek-time");
seekto.innerText = "";
});
$( "#volume-slider").bind( "change", async function(eve) { $( "#volume-slider").bind( "change", async function(eve) {
const slider = eve.target; const slider = eve.target;
let video = document.getElementById("video"); let video = document.getElementById("video");

View File

@ -4,6 +4,7 @@
<li class="menu-option" onclick="goHome()">Home Directory</li> <li class="menu-option" onclick="goHome()">Home Directory</li>
<li id="ctxDownload" class="menu-option" onclick="downloadItem()">Download</li> <li id="ctxDownload" class="menu-option" onclick="downloadItem()">Download</li>
<li id="img2Tab" class="menu-option" onclick="img2Tab()">Image To New Tab</li> <li id="img2Tab" class="menu-option" onclick="img2Tab()">Image To New Tab</li>
<li id="txt2copy" class="menu-option" onclick="text2Clipboard()">Copy</li>
<li class="menu-option" onclick="deleteItem()">Delete</li> <li class="menu-option" onclick="deleteItem()">Delete</li>
</ul> </ul>
</div> </div>

View File

@ -111,6 +111,7 @@
<script src="{{ url_for('static', filename='js/libs/cookie-manager.js')}}"></script> <script src="{{ url_for('static', filename='js/libs/cookie-manager.js')}}"></script>
<script src="{{ url_for('static', filename='js/libs/color-mode-toggler.js')}}"></script> <script src="{{ url_for('static', filename='js/libs/color-mode-toggler.js')}}"></script>
<script src="{{ url_for('static', filename='js/libs/srt-support.js')}}"></script>
<script src="{{ url_for('static', filename='js/webfm/context-menu.js')}}"></script> <script src="{{ url_for('static', filename='js/webfm/context-menu.js')}}"></script>
<script src="{{ url_for('static', filename='js/webfm/backgrounds-manager.js')}}"></script> <script src="{{ url_for('static', filename='js/webfm/backgrounds-manager.js')}}"></script>
<script src="{{ url_for('static', filename='js/webfm/sse.js')}}"></script> <script src="{{ url_for('static', filename='js/webfm/sse.js')}}"></script>

View File

@ -93,9 +93,12 @@
</div> </div>
<div id="video-controls" class="modal-footer"> <div id="video-controls" class="modal-footer">
<div class="col-md-8"> <div class="col-md-6">
<input id="seek-slider" class="form-control-range" style="width: inherit;" type="range" min="0" value="0" max="100" step="1"/> <input id="seek-slider" class="form-control-range" style="width: inherit;" type="range" min="0" value="0" max="100" step="1"/>
</div> </div>
<div class="col-md-2">
<label id="seek-time"></label>
</div>
<div class="col-md-3"> <div class="col-md-3">
<span id="videoCurrentTime"></span> <span id="videoCurrentTime"></span>
/ /