From a937d1f31367496b57f4488c07856bfdcccca71e Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 14 Oct 2021 01:24:48 -0500 Subject: [PATCH 01/12] Video cntrl udates --- src/core/static/css/main.css | 5 +- src/core/static/css/overrides.css | 9 +- src/core/static/js/context-menu.js | 21 ++++- src/core/static/js/ui-logic.js | 16 ++-- src/core/static/js/video-events.js | 85 +++++++------------ src/core/templates/modals/file-modal.html | 37 ++++---- .../shellfm/windows/view/utils/Settings.py | 1 + src/user_config/webfm/settings.json | 5 +- 8 files changed, 87 insertions(+), 92 deletions(-) diff --git a/src/core/static/css/main.css b/src/core/static/css/main.css index 6ee4ea1..bd9dd02 100644 --- a/src/core/static/css/main.css +++ b/src/core/static/css/main.css @@ -20,14 +20,13 @@ } #master-container { - height: 90vh; + height: 80vh; overflow-x: hidden; overflow-y: auto; } #video-controls { - position: relative; - bottom: 2.5em; + background-color: rgba(0, 0, 0, 0.64); } diff --git a/src/core/static/css/overrides.css b/src/core/static/css/overrides.css index 58fc8da..e038c3f 100644 --- a/src/core/static/css/overrides.css +++ b/src/core/static/css/overrides.css @@ -47,10 +47,13 @@ input[type=range][orient=vertical] { +.modal-header { + background-color: rgba(0, 0, 0, 0.64); +} .modal-content { - background-color: #32383e74; - border-color: #f8940674; + background-color: rgba(50, 56, 62, 0.74); + border-color: rgba(249, 148, 6, 0.74); } .sticky-top, @@ -59,7 +62,7 @@ input[type=range][orient=vertical] { } .card-body { - min-height: 326px; + min-height: 326px; font-size: x-large; vertical-align: middle !important; align-items: center; diff --git a/src/core/static/js/context-menu.js b/src/core/static/js/context-menu.js index d39d22c..487e99a 100644 --- a/src/core/static/js/context-menu.js +++ b/src/core/static/js/context-menu.js @@ -20,8 +20,6 @@ window.addEventListener("click", e => { }); window.addEventListener("contextmenu", e => { - e.preventDefault(); - let target = e.target; let elm = target; while (elm.nodeName != "BODY") { @@ -33,9 +31,24 @@ window.addEventListener("contextmenu", e => { } } + let posY = e.pageY; + let posX = e.pageX - 165; + + if (e.pageY > (window.innerHeight - 120)) { + posY = window.innerHeight - 220; + } + + if (e.pageX > (window.innerWidth - 80)) { + posX = window.innerWidth - 320; + } + + if (e.pageX < 80) { + posX = e.pageX + 180; + } + const origin = { - left: e.pageX, - top: e.pageY + left: posX, + top: posY }; setPosition(origin); return false; diff --git a/src/core/static/js/ui-logic.js b/src/core/static/js/ui-logic.js index 99f99d2..398508d 100644 --- a/src/core/static/js/ui-logic.js +++ b/src/core/static/js/ui-logic.js @@ -10,7 +10,14 @@ const downloadItem = (eve) => { } const deleteItem = (eve) => { - let elm = active_card.querySelector('[hash]'); // With attribute named "hash" + if (active_card == null) { + let text = "No card hovered over to delete!"; + let type = "danger"; + displayMessage(text, type, 3); + return ; + } + + let elm = active_card.querySelector('[hash]'); // With attribute named "hash" let elm2 = active_card.querySelector('[title]'); // With attribute named "title" const hash = elm.getAttribute("hash"); const title = elm2.getAttribute("title"); @@ -49,7 +56,6 @@ const closeFile = () => { document.getElementById("image-viewer").style.display = "none"; document.getElementById("text-viewer").style.display = "none"; document.getElementById("pdf-viewer").style.display = "none"; - document.getElementById("video-controls").style.display = "none"; title.innerText = ""; video.style.display = "none"; @@ -62,7 +68,6 @@ const showFile = async (title, hash, extension, type) => { document.getElementById("text-viewer").style.display = "none"; document.getElementById("pdf-viewer").style.display = "none"; document.getElementById("video").style.display = "none"; - document.getElementById("video-controls").style.display = "none"; let titleElm = document.getElementById("selectedFile"); titleElm.innerText = title; @@ -77,7 +82,6 @@ const showFile = async (title, hash, extension, type) => { const setupVideo = async (hash, extension) => { let video = document.getElementById("video"); - let controls = document.getElementById("video-controls"); video.poster = "static/imgs/icons/loading.gif"; video.style.display = ""; video.src = "#" @@ -102,9 +106,9 @@ const setupVideo = async (hash, extension) => { } } + + video.src = video_path; modal.show(); - controls.style.display = "none"; - video.src = video_path; } catch (e) { video.style.display = "none"; console.log(e); diff --git a/src/core/static/js/video-events.js b/src/core/static/js/video-events.js index ecd065a..8c5a429 100644 --- a/src/core/static/js/video-events.js +++ b/src/core/static/js/video-events.js @@ -2,7 +2,6 @@ let fullScreenState = 0; let clicktapwait = 200; let shouldPlay = null; let controlsTimeout = null; -let canHideControls = true; const getTimeFormatted = (duration = null) => { @@ -19,45 +18,17 @@ const getTimeFormatted = (duration = null) => { const togglePlay = (video) => { shouldPlay = setTimeout(function () { - let controls = document.getElementById("video-controls"); - shouldPlay = null; + shouldPlay = null; if (video.paused) { - video.style.cursor = 'none'; - controls.style.display = "none"; + video.style.cursor = 'none'; video.play(); } else { - video.style.cursor = ''; - controls.style.display = ""; + video.style.cursor = ''; video.pause(); } }, 300); } -const showControls = () => { - const video = document.getElementById("video"); - const controls = document.getElementById("video-controls"); - - video.style.cursor = ''; - controls.style.display = ""; - if (controlsTimeout) { - clearTimeout(controlsTimeout); - } - - controlsTimeout = setTimeout(function () { - if (!video.paused) { - if (canHideControls) { - video.style.cursor = 'none'; - controls.style.display = "none"; - controlsTimeout = null; - } else { - showControls(); - } - } - }, 3000); -} - - - const setFullscreenSettings = (parentElm, video) => { parentElm.requestFullscreen(); @@ -73,8 +44,8 @@ const unsetFullscreenSettings = (video) => { } const toggleFullscreen = (video) => { - containerElm = document.getElementById("video-container"); - parentElm = video.parentElement; + let containerElm = document.getElementById("video-container"); + let parentElm = video.parentElement; if (video.requestFullscreen || video.webkitRequestFullscreen || video.msRequestFullscreen) { setFullscreenSettings(parentElm, video); @@ -92,7 +63,7 @@ const toggleFullscreen = (video) => { unsetFullscreenSettings(video); } - fullScreenState = 0; + fullScreenState = 0; } fullScreenState += 1; @@ -109,7 +80,6 @@ const toggleVolumeControl = () => { - $("#video").on("loadedmetadata", function(eve){ const video = eve.target; let videoDuration = document.getElementById("videoDuration"); @@ -117,13 +87,13 @@ $("#video").on("loadedmetadata", function(eve){ }); -$("#video").on("keyup", function(eve) { + +$("#video").on("keydown", function(eve) { + event.preventDefault(); const key = eve.keyCode; const video = eve.target; - if (key === 32 || key === 80) { // Spacebar for pausing - togglePlay(video); - } else if (key === 37) { // Left key for back tracking 5 sec + if (key === 37) { // Left key for back tracking 5 sec video.currentTime -= 5; } else if (key === 39) { // Right key for fast forward 5 sec video.currentTime += 5; @@ -135,6 +105,17 @@ $("#video").on("keyup", function(eve) { if (video.volume >= 0.0) { video.volume -= 0.05; } + } + +}); + + +$("#video").on("keyup", function(eve) { + const key = eve.keyCode; + const video = eve.target; + + if (key === 32 || key === 80) { // Spacebar for pausing + togglePlay(video); } else if (key === 70) { // f key for toggling full screen toggleFullscreen(video); } else if (key === 76) { // l key for toggling loop @@ -195,17 +176,6 @@ $( "#video" ).bind( "timeupdate", async function(eve) { videoDuration.innerText = getTimeFormatted(video.currentTime); }); -// $( "#video" ).bind( "ended", async function(eve) { -// alert("Hello...") -// // let videoDuration = document.getElementById("videoCurrentTime"); -// // const video = eve.target; -// // const seekto = document.getElementById("seek-slider"); -// // const vt = video.currentTime * (100 / video.duration); -// // -// // seekto.value = vt; -// // videoDuration.innerText = getTimeFormatted(video.currentTime); -// }); - $( "#seek-slider" ).bind( "change", async function(eve) { const slider = eve.target; let video = document.getElementById("video"); @@ -220,5 +190,14 @@ $( "#volume-slider" ).bind( "change", async function(eve) { video.volume = volumeto; }); -$( "#video-controls" ).bind( "mouseenter", async function(eve) { canHideControls = false; }); -$( "#video-controls" ).bind( "mouseleave", async function(eve) { canHideControls = true; }); + +// $( "#video" ).bind( "ended", async function(eve) { +// alert("Hello...") +// // let videoDuration = document.getElementById("videoCurrentTime"); +// // const video = eve.target; +// // const seekto = document.getElementById("seek-slider"); +// // const vt = video.currentTime * (100 / video.duration); +// // +// // seekto.value = vt; +// // videoDuration.innerText = getTimeFormatted(video.currentTime); +// }); diff --git a/src/core/templates/modals/file-modal.html b/src/core/templates/modals/file-modal.html index d88ecd6..7ea1a65 100644 --- a/src/core/templates/modals/file-modal.html +++ b/src/core/templates/modals/file-modal.html @@ -19,28 +19,9 @@ autoplay="" volume="0.75" poster="{{ url_for('static', filename='imgs/icons/loading.gif')}}" - onmousemove="showControls()"> + > -
-
-
- -
-
- - / - -
-
- 🔈 - -
-
-
- - + - {% endblock file_modal %} diff --git a/src/core/utils/shellfm/windows/view/utils/Settings.py b/src/core/utils/shellfm/windows/view/utils/Settings.py index a6e3ea8..2760ce4 100644 --- a/src/core/utils/shellfm/windows/view/utils/Settings.py +++ b/src/core/utils/shellfm/windows/view/utils/Settings.py @@ -57,6 +57,7 @@ class Settings: subpath = settings["base_of_home"] HIDE_HIDDEN_FILES = True if settings["hide_hidden_files"] == "true" else False + FFMPG_THUMBNLR = FFMPG_THUMBNLR if settings["thumbnailer_path"] == "" else settings["thumbnailer_path"] go_past_home = True if settings["go_past_home"] == "true" else False lock_folder = True if settings["lock_folder"] == "true" else False locked_folders = settings["locked_folders"].split("::::") diff --git a/src/user_config/webfm/settings.json b/src/user_config/webfm/settings.json index 2ba7a28..43a0f84 100644 --- a/src/user_config/webfm/settings.json +++ b/src/user_config/webfm/settings.json @@ -2,9 +2,10 @@ "settings": { "base_of_home": "/LazyShare", "hide_hidden_files": "true", - "go_past_home": "true", + "thumbnailer_path": "ffmpegthumbnailer", + "go_past_home": "false", "lock_folder": "true", - "locked_folders": "Synced Backup::::venv::::flasks::::Cryptomator", + "locked_folders": "Synced Backup::::venv::::flasks::::Encrypted Vault::::Cryptomator", "mplayer_options": "-quiet -really-quiet -xy 1600 -geometry 50%:50%", "music_app": "/opt/deadbeef/bin/deadbeef", "media_app": "mpv", From 57d52fcddf73f97934561014eb7b112966a022d7 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Thu, 11 Nov 2021 23:32:59 -0600 Subject: [PATCH 02/12] Added cover art and background art scraper --- src/core/routes/Routes.py | 44 ++++- src/core/utils/tmdbscraper/scraper.py | 22 +++ src/core/utils/tmdbscraper/scraper_config.py | 111 +++++++++++ .../utils/tmdbscraper/scraper_datahelper.py | 54 ++++++ src/core/utils/tmdbscraper/scraper_xbmc.py | 175 ++++++++++++++++++ 5 files changed, 404 insertions(+), 2 deletions(-) create mode 100644 src/core/utils/tmdbscraper/scraper.py create mode 100644 src/core/utils/tmdbscraper/scraper_config.py create mode 100644 src/core/utils/tmdbscraper/scraper_datahelper.py create mode 100644 src/core/utils/tmdbscraper/scraper_xbmc.py diff --git a/src/core/routes/Routes.py b/src/core/routes/Routes.py index 306996b..61e3f25 100644 --- a/src/core/routes/Routes.py +++ b/src/core/routes/Routes.py @@ -1,5 +1,5 @@ # Python imports -import os, json, secrets, re, shutil +import os, json, secrets, requests, shutil, re # Lib imports from flask import request, session, render_template, send_from_directory, redirect @@ -11,9 +11,11 @@ from flask_login import current_user from core import app, logger, oidc, db, Favorites # Get from __init__ from core.utils import MessageHandler # Get simple message processor from core.utils.shellfm import WindowController # Get file manager controller +from core.utils.tmdbscraper import scraper # Get media art scraper msgHandler = MessageHandler() +tmdb = scraper.get_tmdb_scraper() window_controllers = {} # valid_fname_pat = re.compile(r"/^[a-zA-Z0-9-_\[\]\(\)| ]+$/") valid_fname_pat = re.compile(r"[a-z0-9A-Z-_\[\]\(\)\| ]{4,20}") @@ -81,16 +83,54 @@ def listFiles(_hash = None): return msgHandler.createMessageJSON("danger", error_msg) - sub_path = view.get_current_sub_path() + sub_path = view.get_current_sub_path() + current_directory = sub_path.split("/")[-1] + if "(" in current_directory and ")" in current_directory: + title = current_directory.split("(")[0].strip() + startIndex = current_directory.index('(') + 1 + endIndex = current_directory.index(')') + date = current_directory[startIndex:endIndex] + video_data = tmdb.search(title, date) + background_url = video_data[0]["backdrop_path"] + background_pth = view.get_current_directory() + "/" + "000.jpg" + + if not os.path.isfile(background_pth): + r = requests.get(background_url, stream = True) + + if r.status_code == 200: + r.raw.decode_content = True + with open(background_pth,'wb') as f: + shutil.copyfileobj(r.raw, f) + + view.load_directory() + print('Cover Background Image sucessfully retreived...') + else: + print('Cover Background Image Couldn\'t be retreived...') + + files = view.get_files_formatted() fave = db.session.query(Favorites).filter_by(link = sub_path).first() in_fave = "true" if fave else "false" + files.update({'in_fave': in_fave}) return files else: msg = "Can't manage the request type..." return msgHandler.createMessageJSON("danger", msg) +@app.route('/api/get-posters', methods=['GET', 'POST']) +def getPosters(): + if request.method == 'POST': + view = get_window_controller().get_window(1).get_view(0) + videos = view.get_videos() + + print(videos) + # tmdb.search("Matrix") + return videos + else: + msg = "Can't manage the request type..." + return msgHandler.createMessageJSON("danger", msg) + @app.route('/api/file-manager-action/<_type>/<_hash>', methods=['GET', 'POST']) def fileManagerAction(_type, _hash = None): view = get_window_controller().get_window(1).get_view(0) diff --git a/src/core/utils/tmdbscraper/scraper.py b/src/core/utils/tmdbscraper/scraper.py new file mode 100644 index 0000000..db25e2b --- /dev/null +++ b/src/core/utils/tmdbscraper/scraper.py @@ -0,0 +1,22 @@ +import json +import sys + +from .lib.tmdbscraper.tmdb import TMDBMovieScraper +from .lib.tmdbscraper.fanarttv import get_details as get_fanarttv_artwork +from .lib.tmdbscraper.imdbratings import get_details as get_imdb_details +from .lib.tmdbscraper.traktratings import get_trakt_ratinginfo +from .scraper_datahelper import combine_scraped_details_info_and_ratings, \ + combine_scraped_details_available_artwork, find_uniqueids_in_text, get_params +from .scraper_config import configure_scraped_details, PathSpecificSettings, \ + configure_tmdb_artwork, is_fanarttv_configured + + + + + + +def get_tmdb_scraper(): + language = 'en-US' + certcountry = 'us' + ADDON_SETTINGS = None + return TMDBMovieScraper(ADDON_SETTINGS, language, certcountry) diff --git a/src/core/utils/tmdbscraper/scraper_config.py b/src/core/utils/tmdbscraper/scraper_config.py new file mode 100644 index 0000000..76a042b --- /dev/null +++ b/src/core/utils/tmdbscraper/scraper_config.py @@ -0,0 +1,111 @@ +def configure_scraped_details(details, settings): + details = _configure_rating_prefix(details, settings) + details = _configure_keeporiginaltitle(details, settings) + details = _configure_trailer(details, settings) + details = _configure_multiple_studios(details, settings) + details = _configure_default_rating(details, settings) + details = _configure_tags(details, settings) + return details + +def configure_tmdb_artwork(details, settings): + if 'available_art' not in details: + return details + + art = details['available_art'] + fanart_enabled = settings.getSettingBool('fanart') + if not fanart_enabled: + if 'fanart' in art: + del art['fanart'] + if 'set.fanart' in art: + del art['set.fanart'] + if not settings.getSettingBool('landscape'): + if 'landscape' in art: + if fanart_enabled: + art['fanart'] = art.get('fanart', []) + art['landscape'] + del art['landscape'] + if 'set.landscape' in art: + if fanart_enabled: + art['set.fanart'] = art.get('set.fanart', []) + art['set.landscape'] + del art['set.landscape'] + + return details + +def is_fanarttv_configured(settings): + return settings.getSettingBool('enable_fanarttv_artwork') + +def _configure_rating_prefix(details, settings): + if details['info'].get('mpaa'): + details['info']['mpaa'] = settings.getSettingString('certprefix') + details['info']['mpaa'] + return details + +def _configure_keeporiginaltitle(details, settings): + if settings.getSettingBool('keeporiginaltitle'): + details['info']['title'] = details['info']['originaltitle'] + return details + +def _configure_trailer(details, settings): + if details['info'].get('trailer') and not settings.getSettingBool('trailer'): + del details['info']['trailer'] + return details + +def _configure_multiple_studios(details, settings): + if not settings.getSettingBool('multiple_studios'): + details['info']['studio'] = details['info']['studio'][:1] + return details + +def _configure_default_rating(details, settings): + imdb_default = bool(details['ratings'].get('imdb')) and settings.getSettingString('RatingS') == 'IMDb' + trakt_default = bool(details['ratings'].get('trakt')) and settings.getSettingString('RatingS') == 'Trakt' + default_rating = 'themoviedb' + if imdb_default: + default_rating = 'imdb' + elif trakt_default: + default_rating = 'trakt' + if default_rating not in details['ratings']: + default_rating = list(details['ratings'].keys())[0] if details['ratings'] else None + for rating_type in details['ratings'].keys(): + details['ratings'][rating_type]['default'] = rating_type == default_rating + return details + +def _configure_tags(details, settings): + if not settings.getSettingBool('add_tags'): + del details['info']['tag'] + return details + +# pylint: disable=invalid-name +try: + basestring +except NameError: # py2 / py3 + basestring = str + +#pylint: disable=redefined-builtin +class PathSpecificSettings(object): + # read-only shim for typed `xbmcaddon.Addon().getSetting*` methods + def __init__(self, settings_dict, log_fn): + self.data = settings_dict + self.log = log_fn + + def getSettingBool(self, id): + return self._inner_get_setting(id, bool, False) + + def getSettingInt(self, id): + return self._inner_get_setting(id, int, 0) + + def getSettingNumber(self, id): + return self._inner_get_setting(id, float, 0.0) + + def getSettingString(self, id): + return self._inner_get_setting(id, basestring, '') + + def _inner_get_setting(self, setting_id, setting_type, default): + value = self.data.get(setting_id) + if isinstance(value, setting_type): + return value + self._log_bad_value(value, setting_id) + return default + + def _log_bad_value(self, value, setting_id): + if value is None: + self.log("requested setting ({0}) was not found.".format(setting_id)) + else: + self.log('failed to load value "{0}" for setting {1}'.format(value, setting_id)) diff --git a/src/core/utils/tmdbscraper/scraper_datahelper.py b/src/core/utils/tmdbscraper/scraper_datahelper.py new file mode 100644 index 0000000..23504e0 --- /dev/null +++ b/src/core/utils/tmdbscraper/scraper_datahelper.py @@ -0,0 +1,54 @@ +import re +try: + from urlparse import parse_qsl +except ImportError: # py2 / py3 + from urllib.parse import parse_qsl + +# get addon params from the plugin path querystring +def get_params(argv): + result = {'handle': int(argv[0])} + if len(argv) < 2 or not argv[1]: + return result + + result.update(parse_qsl(argv[1].lstrip('?'))) + return result + +def combine_scraped_details_info_and_ratings(original_details, additional_details): + def update_or_set(details, key, value): + if key in details: + details[key].update(value) + else: + details[key] = value + + if additional_details: + if additional_details.get('info'): + update_or_set(original_details, 'info', additional_details['info']) + if additional_details.get('ratings'): + update_or_set(original_details, 'ratings', additional_details['ratings']) + return original_details + +def combine_scraped_details_available_artwork(original_details, additional_details): + if additional_details and additional_details.get('available_art'): + available_art = additional_details['available_art'] + if not original_details.get('available_art'): + original_details['available_art'] = available_art + else: + for arttype, artlist in available_art.items(): + original_details['available_art'][arttype] = \ + artlist + original_details['available_art'].get(arttype, []) + + return original_details + +def find_uniqueids_in_text(input_text): + result = {} + res = re.search(r'(themoviedb.org/movie/)([0-9]+)', input_text) + if (res): + result['tmdb'] = res.group(2) + res = re.search(r'imdb....?/title/tt([0-9]+)', input_text) + if (res): + result['imdb'] = 'tt' + res.group(1) + else: + res = re.search(r'imdb....?/Title\?t{0,2}([0-9]+)', input_text) + if (res): + result['imdb'] = 'tt' + res.group(1) + return result diff --git a/src/core/utils/tmdbscraper/scraper_xbmc.py b/src/core/utils/tmdbscraper/scraper_xbmc.py new file mode 100644 index 0000000..c976407 --- /dev/null +++ b/src/core/utils/tmdbscraper/scraper_xbmc.py @@ -0,0 +1,175 @@ +import json +import sys +import xbmc +import xbmcaddon +import xbmcgui +import xbmcplugin + +from lib.tmdbscraper.tmdb import TMDBMovieScraper +from lib.tmdbscraper.fanarttv import get_details as get_fanarttv_artwork +from lib.tmdbscraper.imdbratings import get_details as get_imdb_details +from lib.tmdbscraper.traktratings import get_trakt_ratinginfo +from scraper_datahelper import combine_scraped_details_info_and_ratings, \ + combine_scraped_details_available_artwork, find_uniqueids_in_text, get_params +from scraper_config import configure_scraped_details, PathSpecificSettings, \ + configure_tmdb_artwork, is_fanarttv_configured + +ADDON_SETTINGS = xbmcaddon.Addon() +ID = ADDON_SETTINGS.getAddonInfo('id') + +def log(msg, level=xbmc.LOGDEBUG): + xbmc.log(msg='[{addon}]: {msg}'.format(addon=ID, msg=msg), level=level) + +def get_tmdb_scraper(settings): + language = settings.getSettingString('language') + certcountry = settings.getSettingString('tmdbcertcountry') + return TMDBMovieScraper(ADDON_SETTINGS, language, certcountry) + +def search_for_movie(title, year, handle, settings): + log("Find movie with title '{title}' from year '{year}'".format(title=title, year=year), xbmc.LOGINFO) + title = _strip_trailing_article(title) + search_results = get_tmdb_scraper(settings).search(title, year) + if not search_results: + return + if 'error' in search_results: + header = "The Movie Database Python error searching with web service TMDB" + xbmcgui.Dialog().notification(header, search_results['error'], xbmcgui.NOTIFICATION_WARNING) + log(header + ': ' + search_results['error'], xbmc.LOGWARNING) + return + + for movie in search_results: + listitem = _searchresult_to_listitem(movie) + uniqueids = {'tmdb': str(movie['id'])} + xbmcplugin.addDirectoryItem(handle=handle, url=build_lookup_string(uniqueids), + listitem=listitem, isFolder=True) + +_articles = [prefix + article for prefix in (', ', ' ') for article in ("the", "a", "an")] +def _strip_trailing_article(title): + title = title.lower() + for article in _articles: + if title.endswith(article): + return title[:-len(article)] + return title + +def _searchresult_to_listitem(movie): + movie_info = {'title': movie['title']} + movie_label = movie['title'] + + movie_year = movie['release_date'].split('-')[0] if movie.get('release_date') else None + if movie_year: + movie_label += ' ({})'.format(movie_year) + movie_info['year'] = movie_year + + listitem = xbmcgui.ListItem(movie_label, offscreen=True) + + listitem.setInfo('video', movie_info) + if movie['poster_path']: + listitem.setArt({'thumb': movie['poster_path']}) + + return listitem + +# Low limit because a big list of artwork can cause trouble in some cases +# (a column can be too large for the MySQL integration), +# and how useful is a big list anyway? Not exactly rhetorical, this is an experiment. +IMAGE_LIMIT = 10 + +def add_artworks(listitem, artworks): + for arttype, artlist in artworks.items(): + if arttype == 'fanart': + continue + for image in artlist[:IMAGE_LIMIT]: + listitem.addAvailableArtwork(image['url'], arttype) + + fanart_to_set = [{'image': image['url'], 'preview': image['preview']} + for image in artworks['fanart'][:IMAGE_LIMIT]] + listitem.setAvailableFanart(fanart_to_set) + +def get_details(input_uniqueids, handle, settings): + if not input_uniqueids: + return False + details = get_tmdb_scraper(settings).get_details(input_uniqueids) + if not details: + return False + if 'error' in details: + header = "The Movie Database Python error with web service TMDB" + xbmcgui.Dialog().notification(header, details['error'], xbmcgui.NOTIFICATION_WARNING) + log(header + ': ' + details['error'], xbmc.LOGWARNING) + return False + + details = configure_tmdb_artwork(details, settings) + + if settings.getSettingString('RatingS') == 'IMDb' or settings.getSettingBool('imdbanyway'): + imdbinfo = get_imdb_details(details['uniqueids']) + if 'error' in imdbinfo: + header = "The Movie Database Python error with website IMDB" + log(header + ': ' + imdbinfo['error'], xbmc.LOGWARNING) + else: + details = combine_scraped_details_info_and_ratings(details, imdbinfo) + + if settings.getSettingString('RatingS') == 'Trakt' or settings.getSettingBool('traktanyway'): + traktinfo = get_trakt_ratinginfo(details['uniqueids']) + details = combine_scraped_details_info_and_ratings(details, traktinfo) + + if is_fanarttv_configured(settings): + fanarttv_info = get_fanarttv_artwork(details['uniqueids'], + settings.getSettingString('fanarttv_clientkey'), + settings.getSettingString('fanarttv_language'), + details['_info']['set_tmdbid']) + details = combine_scraped_details_available_artwork(details, fanarttv_info) + + details = configure_scraped_details(details, settings) + + listitem = xbmcgui.ListItem(details['info']['title'], offscreen=True) + listitem.setInfo('video', details['info']) + listitem.setCast(details['cast']) + listitem.setUniqueIDs(details['uniqueids'], 'tmdb') + add_artworks(listitem, details['available_art']) + + for rating_type, value in details['ratings'].items(): + if 'votes' in value: + listitem.setRating(rating_type, value['rating'], value['votes'], value['default']) + else: + listitem.setRating(rating_type, value['rating'], defaultt=value['default']) + + xbmcplugin.setResolvedUrl(handle=handle, succeeded=True, listitem=listitem) + return True + +def find_uniqueids_in_nfo(nfo, handle): + uniqueids = find_uniqueids_in_text(nfo) + if uniqueids: + listitem = xbmcgui.ListItem(offscreen=True) + xbmcplugin.addDirectoryItem( + handle=handle, url=build_lookup_string(uniqueids), listitem=listitem, isFolder=True) + +def build_lookup_string(uniqueids): + return json.dumps(uniqueids) + +def parse_lookup_string(uniqueids): + try: + return json.loads(uniqueids) + except ValueError: + log("Can't parse this lookup string, is it from another add-on?\n" + uniqueids, xbmc.LOGWARNING) + return None + +def run(): + params = get_params(sys.argv[1:]) + enddir = True + if 'action' in params: + settings = ADDON_SETTINGS if not params.get('pathSettings') else \ + PathSpecificSettings(json.loads(params['pathSettings']), lambda msg: log(msg, xbmc.LOGWARNING)) + action = params["action"] + if action == 'find' and 'title' in params: + search_for_movie(params["title"], params.get("year"), params['handle'], settings) + elif action == 'getdetails' and 'url' in params: + enddir = not get_details(parse_lookup_string(params["url"]), params['handle'], settings) + elif action == 'NfoUrl' and 'nfo' in params: + find_uniqueids_in_nfo(params["nfo"], params['handle']) + else: + log("unhandled action: " + action, xbmc.LOGWARNING) + else: + log("No action in 'params' to act on", xbmc.LOGWARNING) + if enddir: + xbmcplugin.endOfDirectory(params['handle']) + +if __name__ == '__main__': + run() From 978d4de21223c50168bc820ef6d5413994076f96 Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 12 Nov 2021 01:42:08 -0600 Subject: [PATCH 03/12] Updated video container responsiveness --- src/core/static/css/main.css | 11 +++++++++-- src/core/static/js/video-events.js | 15 +++++++++++---- 2 files changed, 20 insertions(+), 6 deletions(-) diff --git a/src/core/static/css/main.css b/src/core/static/css/main.css index bd9dd02..df670ca 100644 --- a/src/core/static/css/main.css +++ b/src/core/static/css/main.css @@ -48,6 +48,12 @@ } +.page-alert-zone-container { + position: sticky; + top: 0em; + z-index: 2; +} + .card-title-text { white-space: nowrap; @@ -68,11 +74,12 @@ } .viewer { - max-width: 55em; + max-width: 100% !important; + height: auto !important; + max-height: 100% !important; } - /* Other message text colors */ .errorTxt { color: rgb(170, 18, 18); } .warningTxt { color: rgb(255, 168, 0); } diff --git a/src/core/static/js/video-events.js b/src/core/static/js/video-events.js index 8c5a429..a4ee4fd 100644 --- a/src/core/static/js/video-events.js +++ b/src/core/static/js/video-events.js @@ -33,14 +33,21 @@ const togglePlay = (video) => { const setFullscreenSettings = (parentElm, video) => { parentElm.requestFullscreen(); video.classList.remove("viewer"); - video.style.cursor = 'none'; - video.style.height = 'inherit'; + video.style.cursor = 'none'; + video.style.height = 'inherit'; + video.style.width = 'inherit'; + + if(/Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent)){ + video.style.transform = 'rotate(90deg)'; + } } const unsetFullscreenSettings = (video) => { video.classList.add("viewer"); - video.style.cursor = ''; - video.style.height = ''; + video.style.transform = ''; + video.style.cursor = ''; + video.style.height = ''; + video.style.width = ''; } const toggleFullscreen = (video) => { From b30a8f4b447e59cfd5682821716f6c22d4e885aa Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Fri, 18 Mar 2022 17:18:13 -0500 Subject: [PATCH 04/12] added size info, new sort logic --- src/core/static/css/main.css | 5 +++ src/core/static/js/react-ui-logic.js | 16 +++++-- src/core/utils/shellfm/windows/view/View.py | 46 +++++++++++++++++---- 3 files changed, 56 insertions(+), 11 deletions(-) diff --git a/src/core/static/css/main.css b/src/core/static/css/main.css index df670ca..7e471bb 100644 --- a/src/core/static/css/main.css +++ b/src/core/static/css/main.css @@ -79,6 +79,11 @@ max-height: 100% !important; } +.float-right { + float: right; + text-align: right; + font-size: initial; +} /* Other message text colors */ .errorTxt { color: rgb(170, 18, 18); } diff --git a/src/core/static/js/react-ui-logic.js b/src/core/static/js/react-ui-logic.js index 8121825..57fc44c 100644 --- a/src/core/static/js/react-ui-logic.js +++ b/src/core/static/js/react-ui-logic.js @@ -54,6 +54,7 @@ class FilesList extends React.Component { for (let file of files) { const name = file[0]; const hash = file[1]; + const fsize = file[2]; let extension = re.exec( name.toLowerCase() )[1] ? name : "file.dir"; let data = setFileIconType(extension); let icon = data[0]; @@ -63,13 +64,22 @@ class FilesList extends React.Component { if (filetype === "video") { card_header = name; - card_body = {name}; + card_body = + {name} + {fsize} + ; } else if (filetype === "image") { card_header = name; - card_body = {name}; + card_body = + {name} + {fsize} + ; } else { card_header = {name}; - card_body = name; + card_body = + {name} + {fsize} + ; } diff --git a/src/core/utils/shellfm/windows/view/View.py b/src/core/utils/shellfm/windows/view/View.py index 2a37a5f..70a97d4 100644 --- a/src/core/utils/shellfm/windows/view/View.py +++ b/src/core/utils/shellfm/windows/view/View.py @@ -1,6 +1,5 @@ # Python imports -import hashlib -import os +import hashlib, os, re from os import listdir from os.path import isdir, isfile, join @@ -56,11 +55,11 @@ class View(Settings, Launcher, Path): else: self.dirs.append(f) - self.dirs.sort() - self.vids.sort() - self.images.sort() - self.desktop.sort() - self.ungrouped.sort() + self.dirs.sort(key=self._natural_keys) + self.vids.sort(key=self._natural_keys) + self.images.sort(key=self._natural_keys) + self.desktop.sort(key=self._natural_keys) + self.ungrouped.sort(key=self._natural_keys) self.files = self.dirs + self.vids + self.images + self.desktop + self.ungrouped @@ -68,9 +67,12 @@ class View(Settings, Launcher, Path): return hashlib.sha256(str.encode(text)).hexdigest()[:18] def hashSet(self, arry): + path = self.get_path() data = [] for arr in arry: - data.append([arr, self.hashText(arr)]) + file = f"{path}/{arr}" + size = "4K" if isdir(file) else self.sizeof_fmt(os.path.getsize(file)) + data.append([arr, self.hashText(arr), size]) return data def get_path_part_from_hash(self, hash): @@ -167,3 +169,31 @@ class View(Settings, Launcher, Path): def get_ungrouped(self): return self.hashSet(self.ungrouped) + + def sizeof_fmt(self, num, suffix="B"): + for unit in ["", "K", "M", "G", "T", "Pi", "Ei", "Zi"]: + if abs(num) < 1024.0: + return f"{num:3.1f} {unit}{suffix}" + num /= 1024.0 + return f"{num:.1f} Yi{suffix}" + + def get_dir_size(self, sdir): + """Get the size of a directory. Based on code found online.""" + size = os.path.getsize(sdir) + + for item in listdir(sdir): + item = join(sdir, item) + + if isfile(item): + size = size + os.path.getsize(item) + elif isdir(item): + size = size + self.get_dir_size(item) + + return size + + + def _atoi(self, text): + return int(text) if text.isdigit() else text + + def _natural_keys(self, text): + return [ self._atoi(c) for c in re.split('(\d+)',text) ] From 22c437e8edbe7ee045ff91825abd95f3e18b1dcd Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 13 Jun 2022 21:51:51 -0500 Subject: [PATCH 05/12] addedd rtsp streamer inferastructure, applied fstrings, updated method names to align with standards in project --- src/core/config.py | 10 +- src/core/routes/Routes.py | 86 +++--- src/core/utils/MessageHandler.py | 2 +- .../utils/shellfm/windows/WindowController.py | 12 +- src/core/utils/shellfm/windows/view/View.py | 2 +- .../shellfm/windows/view/utils/Launcher.py | 25 +- .../shellfm/windows/view/utils/Settings.py | 22 +- .../windows/view/utils/rtsp-simple-server | Bin 0 -> 20226064 bytes .../windows/view/utils/rtsp-simple-server.yml | 281 ++++++++++++++++++ 9 files changed, 367 insertions(+), 73 deletions(-) create mode 100755 src/core/utils/shellfm/windows/view/utils/rtsp-simple-server create mode 100644 src/core/utils/shellfm/windows/view/utils/rtsp-simple-server.yml diff --git a/src/core/config.py b/src/core/config.py index 7f2472e..acd6569 100644 --- a/src/core/config.py +++ b/src/core/config.py @@ -29,7 +29,7 @@ class Config(object): LOGIN_PATH = "OIDC" # Value can be OIDC or FLASK_LOGIN OIDC_TOKEN_TYPE_HINT = 'access_token' APP_REDIRECT_URI = "https%3A%2F%2Fwww.webfm.com%2F" # This path is submitted as the redirect URI in certain code flows - OIDC_CLIENT_SECRETS = ROOT_FILE_PTH + '/client_secrets.json' + OIDC_CLIENT_SECRETS = f'{ROOT_FILE_PTH}/client_secrets.json' OIDC_ID_TOKEN_COOKIE_SECURE = True OIDC_REQUIRE_VERIFIED_EMAIL = False OIDC_USER_INFO_ENABLED = True @@ -38,14 +38,14 @@ class Config(object): 'https://www.ssoapps.com/auth/realms/apps' ] - STATIC_FPTH = ROOT_FILE_PTH + "/static" + STATIC_FPTH = f"{ROOT_FILE_PTH}/static" REL_THUMBS_PTH = "static/imgs/thumbnails" # Used for flask thumbnail return # We are overiding some of the the shellmen view settings with these to make it all work with flask. # These are passed along to the shellmen view from the Routes file upon the window controller creation. - ABS_THUMBS_PTH = STATIC_FPTH + "/imgs/thumbnails" # Used for thumbnail generation - REMUX_FOLDER = STATIC_FPTH + "/remuxs" # Remuxed files folder - FFMPG_THUMBNLR = STATIC_FPTH + "/ffmpegthumbnailer" # Thumbnail generator binary + ABS_THUMBS_PTH = f"{STATIC_FPTH}/imgs/thumbnails" # Used for thumbnail generation + REMUX_FOLDER = f"{STATIC_FPTH}/remuxs" # Remuxed files folder + FFMPG_THUMBNLR = f"{STATIC_FPTH}/ffmpegthumbnailer" # Thumbnail generator binary diff --git a/src/core/routes/Routes.py b/src/core/routes/Routes.py index 61e3f25..ad9e526 100644 --- a/src/core/routes/Routes.py +++ b/src/core/routes/Routes.py @@ -67,11 +67,11 @@ def listFiles(_hash = None): msg = "Log in with an Admin privlidged user to view the requested path!" is_locked = view.is_folder_locked(_hash) if is_locked and not oidc.user_loggedin: - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) elif is_locked and oidc.user_loggedin: isAdmin = oidc.user_getfield("isAdmin") if isAdmin != "yes" : - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) if dot_dots[0][1] != _hash and dot_dots[1][1] != _hash: path = view.get_path_part_from_hash(_hash) @@ -80,7 +80,7 @@ def listFiles(_hash = None): error_msg = view.get_error_message() if error_msg != None: view.unset_error_message() - return msgHandler.createMessageJSON("danger", error_msg) + return msgHandler.create_JSON_message("danger", error_msg) sub_path = view.get_current_sub_path() @@ -92,7 +92,7 @@ def listFiles(_hash = None): date = current_directory[startIndex:endIndex] video_data = tmdb.search(title, date) background_url = video_data[0]["backdrop_path"] - background_pth = view.get_current_directory() + "/" + "000.jpg" + background_pth = f"{view.get_current_directory()}/000.jpg" if not os.path.isfile(background_pth): r = requests.get(background_url, stream = True) @@ -116,20 +116,17 @@ def listFiles(_hash = None): return files else: msg = "Can't manage the request type..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) @app.route('/api/get-posters', methods=['GET', 'POST']) def getPosters(): if request.method == 'POST': view = get_window_controller().get_window(1).get_view(0) videos = view.get_videos() - - print(videos) - # tmdb.search("Matrix") return videos else: msg = "Can't manage the request type..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) @app.route('/api/file-manager-action/<_type>/<_hash>', methods=['GET', 'POST']) def fileManagerAction(_type, _hash = None): @@ -138,7 +135,7 @@ def fileManagerAction(_type, _hash = None): if _type == "reset-path" and _hash == "None": view.set_to_home() msg = "Returning to home directory..." - return msgHandler.createMessageJSON("success", msg) + return msgHandler.create_JSON_message("success", msg) folder = view.get_current_directory() file = view.get_path_part_from_hash(_hash) @@ -146,34 +143,39 @@ def fileManagerAction(_type, _hash = None): logger.debug(fpath) if _type == "files": - return send_from_directory(folder, file) + logger.debug(f"Downloading:\n\tDirectory: {folder}\n\tFile: {file}") + return send_from_directory(directory=folder, filename=file) if _type == "remux": # NOTE: Need to actually implimint a websocket to communicate back to client that remux has completed. # As is, the remux thread hangs until completion and client tries waiting until server reaches connection timeout. # I.E....this is stupid but for now works better than nothing - good_result = view.remuxVideo(_hash, fpath) + good_result = view.remux_video(_hash, fpath) if good_result: return '{"path":"static/remuxs/' + _hash + '.mp4"}' else: msg = "Remuxing: Remux failed or took too long; please, refresh the page and try again..." - return msgHandler.createMessageJSON("success", msg) - if _type == "run-locally": - msg = "Opened media..." - view.openFilelocally(fpath) - return msgHandler.createMessageJSON("success", msg) + return msgHandler.create_JSON_message("success", msg) + + if _type == "remux": + stream_target = view.remux_video(_hash, fpath) # NOTE: Positionally protecting actions further down that are privlidged # Be aware of ordering! msg = "Log in with an Admin privlidged user to do this action!" if not oidc.user_loggedin: - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) elif oidc.user_loggedin: isAdmin = oidc.user_getfield("isAdmin") if isAdmin != "yes" : - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) + if _type == "run-locally": + msg = "Opened media..." + view.open_file_locally(fpath) + return msgHandler.create_JSON_message("success", msg) + if _type == "delete": try: msg = f"[Success] Deleted the file/folder -->: {file} !" @@ -181,10 +183,10 @@ def fileManagerAction(_type, _hash = None): os.unlink(fpath) else: shutil.rmtree(fpath) - return msgHandler.createMessageJSON("success", msg) + return msgHandler.create_JSON_message("success", msg) except Exception as e: msg = "[Error] Unable to delete the file/folder...." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) @app.route('/api/list-favorites', methods=['GET', 'POST']) @@ -198,7 +200,7 @@ def listFavorites(): return '{"faves_list":' + json.dumps(faves) + '}' else: msg = "Can't manage the request type..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) @app.route('/api/load-favorite/<_id>', methods=['GET', 'POST']) def loadFavorite(_id): @@ -212,10 +214,10 @@ def loadFavorite(_id): except Exception as e: print(repr(e)) msg = "Incorrect Favorites ID..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) else: msg = "Can't manage the request type..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) @app.route('/api/manage-favorites/<_action>', methods=['GET', 'POST']) @@ -235,13 +237,13 @@ def manageFavorites(_action): msg = "Deleted from Favorites successfully..." else: msg = "Couldn't handle action for favorites item..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) db.session.commit() - return msgHandler.createMessageJSON("success", msg) + return msgHandler.create_JSON_message("success", msg) else: msg = "Can't manage the request type..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) @app.route('/api/create/<_type>', methods=['GET', 'POST']) @@ -249,42 +251,42 @@ def create_item(_type = None): if request.method == 'POST': msg = "Log in with an Admin privlidged user to upload files!" if not oidc.user_loggedin: - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) elif oidc.user_loggedin: isAdmin = oidc.user_getfield("isAdmin") if isAdmin != "yes" : - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) TYPE = _type.strip() FNAME = str(request.values['fname']).strip() if not re.fullmatch(valid_fname_pat, FNAME): msg = "A new item name can only contain alphanumeric, -, _, |, [], (), or spaces and must be minimum of 4 and max of 20 characters..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) view = get_window_controller().get_window(1).get_view(0) folder = view.get_current_directory() - new_item = folder + '/' + FNAME + new_item = f"{folder}/{FNAME}" try: if TYPE == "dir": os.mkdir(new_item) elif TYPE == "file": - open(new_item + ".txt", 'a').close() + open(f"{new_item}.txt", 'a').close() else: msg = "Couldn't handle action type for api create..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) except Exception as e: print(repr(e)) msg = "Couldn't create file/folder. An unexpected error occured..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) msg = "[Success] created the file/dir..." - return msgHandler.createMessageJSON("success", msg) + return msgHandler.create_JSON_message("success", msg) else: msg = "Can't manage the request type..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) @app.route('/upload', methods=['GET', 'POST']) @@ -292,15 +294,15 @@ def upload(): if request.method == 'POST' and len(request.files) > 0: msg = "Log in with an Admin privlidged user to upload files!" if not oidc.user_loggedin: - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) elif oidc.user_loggedin: isAdmin = oidc.user_getfield("isAdmin") if isAdmin != "yes" : - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) view = get_window_controller().get_window(1).get_view(0) folder = view.get_current_directory() - UPLOADS_PTH = folder + '/' + UPLOADS_PTH = f'{folder}/' files = UploadSet('files', ALL, default_dest=lambda x: UPLOADS_PTH) configure_uploads(app, files) @@ -310,10 +312,10 @@ def upload(): except Exception as e: print(repr(e)) msg = "[Error] Failed to upload some or all of the file(s)..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) msg = "[Success] Uploaded file(s)..." - return msgHandler.createMessageJSON("success", msg) + return msgHandler.create_JSON_message("success", msg) else: msg = "Can't manage the request type..." - return msgHandler.createMessageJSON("danger", msg) + return msgHandler.create_JSON_message("danger", msg) diff --git a/src/core/utils/MessageHandler.py b/src/core/utils/MessageHandler.py index f6538f2..9bf6368 100644 --- a/src/core/utils/MessageHandler.py +++ b/src/core/utils/MessageHandler.py @@ -10,5 +10,5 @@ class MessageHandler: print("MessageHandler initialized...") - def createMessageJSON(self, type, text): + def create_JSON_message(self, type, text): return '{"message": { "type": "' + type + '", "text": "' + text + '" } }' diff --git a/src/core/utils/shellfm/windows/WindowController.py b/src/core/utils/shellfm/windows/WindowController.py index 30cc30d..4c81aa0 100644 --- a/src/core/utils/shellfm/windows/WindowController.py +++ b/src/core/utils/shellfm/windows/WindowController.py @@ -11,7 +11,7 @@ class WindowController: if window.id == win_id: return window - raise("No Window by ID {} found!".format(win_id)) + raise(f"No Window by ID {win_id} found!") def get_windows(self): return self.windows @@ -19,7 +19,7 @@ class WindowController: def add_window(self): window = Window() window.id = len(self.windows) + 1 - window.name = "window_" + str(window.id) + window.name = f"window_{window.id}" window.create_view() self.windows.append(window) @@ -48,10 +48,10 @@ class WindowController: def list_windows(self): for window in self.windows: print("\n[ Window ]") - print("ID: " + str(window.id)) - print("Name: " + window.name) - print("Nickname: " + window.nickname) - print("View Count: " + str( len(window.views) )) + print("ID: {window.id}") + print("Name: {window.name}") + print("Nickname: {window.nickname}") + print("View Count: {len(window.views)}") def list_views_from_window(self, win_id): diff --git a/src/core/utils/shellfm/windows/view/View.py b/src/core/utils/shellfm/windows/view/View.py index 70a97d4..1f1001a 100644 --- a/src/core/utils/shellfm/windows/view/View.py +++ b/src/core/utils/shellfm/windows/view/View.py @@ -157,7 +157,7 @@ class View(Settings, Launcher, Path): if not os.path.exists(hashImgPth) : fullPath = join(current_directory, video[0]) self.logger.debug(f"Hash Path: {hashImgPth}\nFile Path: {fullPath}") - self.generateVideoThumbnail(fullPath, hashImgPth) + self.generate_video_thumbnail(fullPath, hashImgPth) return videos_set diff --git a/src/core/utils/shellfm/windows/view/utils/Launcher.py b/src/core/utils/shellfm/windows/view/utils/Launcher.py index 6e3dcac..cc09d61 100644 --- a/src/core/utils/shellfm/windows/view/utils/Launcher.py +++ b/src/core/utils/shellfm/windows/view/utils/Launcher.py @@ -9,7 +9,7 @@ import os, subprocess, threading class Launcher: - def openFilelocally(self, file): + def open_file_locally(self, file): lowerName = file.lower() command = [] @@ -38,14 +38,25 @@ class Launcher: subprocess.Popen(command, start_new_session=True, stdout=DEVNULL, stderr=DEVNULL, close_fds=True) - def remuxVideo(self, hash, file): - remux_vid_pth = self.REMUX_FOLDER + "/" + hash + ".mp4" + def create_stream(self, hash, file): + # ffmpeg -re -stream_loop -1 -i "