Merge pull request 'plugin work' (#7) from develop into master

Reviewed-on: #7
This commit is contained in:
itdominator 2022-09-05 06:09:39 +00:00
commit bebe0c7cba
16 changed files with 1174 additions and 270 deletions

View File

@ -1,11 +1,11 @@
{
"manifest": {
"name": "Favorites Plugin",
"name": "Favorites",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {
"ui_target": "plugin_control_list",
"ui_target": "main_menu_bttn_box_bar",
"pass_fm_events": "true",
"pass_ui_objects": ["path_entry"],
"bind_keys": []

View File

@ -26,7 +26,7 @@ def daemon_threaded(fn):
class Plugin:
def __init__(self):
self.name = "Favorites Plugin" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
self.name = "Favorites" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
self.path = os.path.dirname(os.path.realpath(__file__))
self._GLADE_FILE = f"{self.path}/favorites.glade"

View File

@ -0,0 +1,3 @@
"""
Pligin Module
"""

View File

@ -0,0 +1,3 @@
"""
Pligin Package
"""

View File

@ -0,0 +1,12 @@
{
"manifest": {
"name": "Movie/TV Info",
"author": "ITDominator",
"version": "0.0.1",
"support": "",
"requests": {
"ui_target": "context_menu",
"pass_fm_events": "true"
}
}
}

View File

@ -0,0 +1,220 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- Generated with glade 3.38.2 -->
<interface>
<requires lib="gtk+" version="3.16"/>
<object class="GtkImage" id="image1">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="stock">gtk-media-play</property>
</object>
<object class="GtkAdjustment" id="scrub_step_adjuster">
<property name="lower">1</property>
<property name="upper">100</property>
<property name="value">65</property>
<property name="step-increment">1</property>
<property name="page-increment">10</property>
</object>
<object class="GtkTextBuffer" id="textbuffer"/>
<object class="GtkDialog" id="info_dialog">
<property name="can-focus">False</property>
<property name="border-width">6</property>
<property name="title" translatable="yes">Movie / TV Info</property>
<property name="modal">True</property>
<property name="window-position">center-on-parent</property>
<property name="default-width">420</property>
<property name="destroy-with-parent">True</property>
<property name="type-hint">dialog</property>
<property name="skip-taskbar-hint">True</property>
<property name="skip-pager-hint">True</property>
<property name="gravity">center</property>
<child internal-child="vbox">
<object class="GtkBox" id="dialog_vbox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">12</property>
<child internal-child="action_area">
<object class="GtkButtonBox" id="dialog_action_area">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="layout-style">end</property>
<child>
<object class="GtkButton" id="cancel_button">
<property name="label">gtk-close</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="can-default">True</property>
<property name="receives-default">False</property>
<property name="use-stock">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">False</property>
<property name="pack-type">end</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkImage" id="thumbnail_preview_img">
<property name="height-request">320</property>
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">5</property>
<property name="stock">gtk-missing-image</property>
<property name="icon_size">6</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkTextView" id="overview_textview">
<property name="height-request">120</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-start">5</property>
<property name="margin-end">5</property>
<property name="margin-top">10</property>
<property name="margin-bottom">10</property>
<property name="wrap-mode">word</property>
<property name="cursor-visible">False</property>
<property name="buffer">textbuffer</property>
<property name="overwrite">True</property>
<property name="monospace">True</property>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkTable" id="general_table">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="border-width">4</property>
<property name="n-rows">3</property>
<property name="n-columns">2</property>
<property name="column-spacing">12</property>
<property name="row-spacing">6</property>
<child>
<object class="GtkLinkButton" id="trailer_link">
<property name="label" translatable="yes">Trailer</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">image1</property>
</object>
<packing>
<property name="right-attach">2</property>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;File _Name:&lt;/b&gt;</property>
<property name="use-markup">True</property>
<property name="use-underline">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="top-attach">1</property>
<property name="bottom-attach">2</property>
<property name="x-options">GTK_FILL</property>
<property name="y-options"/>
</packing>
</child>
<child>
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="label" translatable="yes">&lt;b&gt;_Location:&lt;/b&gt;</property>
<property name="use-markup">True</property>
<property name="use-underline">True</property>
<property name="xalign">0</property>
</object>
<packing>
<property name="top-attach">2</property>
<property name="bottom-attach">3</property>
<property name="x-options">GTK_FILL</property>
<property name="y-options"/>
</packing>
</child>
<child>
<object class="GtkEntry" id="file_name">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="right-attach">2</property>
<property name="top-attach">1</property>
<property name="bottom-attach">2</property>
<property name="y-options"/>
</packing>
</child>
<child>
<object class="GtkEntry" id="file_location">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="editable">False</property>
</object>
<packing>
<property name="left-attach">1</property>
<property name="right-attach">2</property>
<property name="top-attach">2</property>
<property name="bottom-attach">3</property>
<property name="x-options">GTK_FILL</property>
<property name="y-options"/>
</packing>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
</packing>
</child>
</object>
<packing>
<property name="expand">True</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
</object>
</child>
<action-widgets>
<action-widget response="-7">cancel_button</action-widget>
</action-widgets>
</object>
</interface>

View File

@ -0,0 +1,216 @@
# Python imports
import os, threading, subprocess, time, inspect, requests, shutil
# Lib imports
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('GdkPixbuf', '2.0')
from gi.repository import Gtk, GLib, GdkPixbuf
# Application imports
from tmdbscraper import scraper
# NOTE: Threads WILL NOT die with parent's destruction.
def threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=False).start()
return wrapper
# NOTE: Threads WILL die with parent's destruction.
def daemon_threaded(fn):
def wrapper(*args, **kwargs):
threading.Thread(target=fn, args=args, kwargs=kwargs, daemon=True).start()
return wrapper
class Plugin:
def __init__(self):
self.path = os.path.dirname(os.path.realpath(__file__))
self.name = "Movie/TV Info" # NOTE: Need to remove after establishing private bidirectional 1-1 message bus
# where self.name should not be needed for message comms
self._GLADE_FILE = f"{self.path}/movie_tv_info.glade"
self._builder = None
self._dialog = None
self._thumbnail_preview_img = None
self._tmdb = scraper.get_tmdb_scraper()
self._state = None
self._overview = None
self._file_name = None
self._file_location = None
self._trailer_link = None
self._event_system = None
self._event_sleep_time = .5
self._event_message = None
def get_ui_element(self):
self._builder = Gtk.Builder()
self._builder.add_from_file(self._GLADE_FILE)
classes = [self]
handlers = {}
for c in classes:
methods = None
try:
methods = inspect.getmembers(c, predicate=inspect.ismethod)
handlers.update(methods)
except Exception as e:
print(repr(e))
self._builder.connect_signals(handlers)
self._thumbnailer_dialog = self._builder.get_object("info_dialog")
self._overview = self._builder.get_object("textbuffer")
self._file_name = self._builder.get_object("file_name")
self._file_location = self._builder.get_object("file_location")
self._thumbnail_preview_img = self._builder.get_object("thumbnail_preview_img")
self._file_hash = self._builder.get_object("file_hash")
self._trailer_link = self._builder.get_object("trailer_link")
button = Gtk.Button(label=self.name)
button.connect("button-release-event", self._show_info_page)
return button
def set_fm_event_system(self, fm_event_system):
self._event_system = fm_event_system
def run(self):
self._module_event_observer()
@threaded
def _show_info_page(self, widget=None, eve=None):
self._event_system.push_gui_event([self.name, "get_current_state", ()])
self.wait_for_fm_message()
state = self._event_message
self._event_message = None
GLib.idle_add(self._process_changes, (state))
def _process_changes(self, state):
self._state = None
if len(state.selected_files) == 1:
self._state = state
self._set_ui_data()
response = self._thumbnailer_dialog.run()
if response in [Gtk.ResponseType.CLOSE, Gtk.ResponseType.DELETE_EVENT]:
self._thumbnailer_dialog.hide()
def _set_ui_data(self):
title, path, trailer, video_data = self.get_video_data()
keys = video_data.keys() if video_data else None
overview_text = video_data["overview"] if video_data else f"...NO {self.name.upper()} DATA..."
self.set_text_data(title, path, overview_text)
self.set_thumbnail(video_data) if video_data else ...
self.set_trailer_link(trailer)
print(video_data["videos"]) if not keys in ("", None) and "videos" in keys else ...
def get_video_data(self):
uri = self._state.selected_files[0]
path = self._state.tab.get_current_directory()
parts = uri.split("/")
_title = parts[ len(parts) - 1 ]
try:
title = _title.split("(")[0].strip()
startIndex = _title.index('(') + 1
endIndex = _title.index(')')
date = title[startIndex:endIndex]
except Exception as e:
print(repr(e))
title = _title
date = None
try:
video_data = self._tmdb.search(title, date)[0]
video_id = video_data["id"]
try:
results = self._tmdb.tmdbapi.get_movie(str(video_id), append_to_response="videos")["videos"]["results"]
for result in results:
if "YouTube" in result["site"]:
trailer = result["key"]
if not trailer:
raise Exception("No key found. Defering to none...")
except Exception as e:
print("No trailer found...")
trailer = None
except Exception as e:
print(repr(e))
video_data = None
return title, path, trailer, video_data
def set_text_data(self, title, path, overview_text):
self._file_name.set_text(title)
self._file_location.set_text(path)
self._overview.set_text(overview_text)
@threaded
def set_thumbnail(self, video_data):
background_url = video_data["backdrop_path"]
# background_url = video_data["poster_path"]
background_pth = "/tmp/sfm_mvtv_info.jpg"
try:
os.remove(background_pth)
except Exception as e:
...
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)
print('Cover Background Image sucessfully retreived...')
preview_pixbuf = GdkPixbuf.Pixbuf.new_from_file(background_pth)
self._thumbnail_preview_img.set_from_pixbuf(preview_pixbuf)
else:
print('Cover Background Image Couldn\'t be retreived...')
def set_trailer_link(self, trailer):
if trailer:
self._trailer_link.set_uri(f"https://www.youtube.com/watch?v={trailer}")
def wait_for_fm_message(self):
while not self._event_message:
pass
@daemon_threaded
def _module_event_observer(self):
while True:
time.sleep(self._event_sleep_time)
event = self._event_system.read_module_event()
if event:
try:
if event[0] == self.name:
target_id, method_target, data = self._event_system.consume_module_event()
if not method_target:
self._event_message = data
else:
method = getattr(self.__class__, f"{method_target}")
if data:
data = method(*(self, *data))
else:
method(*(self,))
except Exception as e:
print(repr(e))

View File

@ -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)

View File

@ -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))

View File

@ -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

View File

@ -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()

View File

@ -10,6 +10,12 @@ from utils.settings import Settings
from core.controller import Controller
class App_Launch_Exception(Exception):
...
class Controller_Start_Exceptio(Exception):
...
class Application(IPCServer):
""" Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """
@ -32,7 +38,7 @@ class Application(IPCServer):
message = f"FILE|{args.new_tab}"
self.send_ipc_message(message)
raise Exception("IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/solarfm-ipc.sock")
raise App_Launch_Exception("IPC Server Exists: Will send path(s) to it and close...\nNote: If no fm exists, remove /tmp/solarfm-ipc.sock")
settings = Settings()
@ -40,7 +46,7 @@ class Application(IPCServer):
controller = Controller(args, unknownargs, settings)
if not controller:
raise Exception("Controller exited and doesn't exist...")
raise Controller_Start_Exceptio("Controller exited and doesn't exist...")
# Gets the methods from the classes and sets to handler.
# Then, builder connects to any signals it needs.

View File

@ -50,7 +50,8 @@ class ManifestProcessor:
if "ui_target" in keys:
if requests["ui_target"] in [
"none", "other", "main_Window", "main_menu_bar", "path_menu_bar", "plugin_control_list",
"none", "other", "main_Window", "main_menu_bar",
"main_menu_bttn_box_bar", "path_menu_bar", "plugin_control_list",
"context_menu", "window_1", "window_2", "window_3", "window_4"
]:
if requests["ui_target"] == "other":

View File

@ -65,7 +65,9 @@ class Plugins:
def load_plugin_module(self, path, folder, target):
os.chdir(path)
sys.path.insert(0, path)
sys.path.insert(0, path) # NOTE: I think I'm not using this correctly...
# The folder and target aren't working to create parent package references, so using as stopgap.
# The above is probably polutling import logic and will cause unforseen import issues.
spec = importlib.util.spec_from_file_location(folder, target)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)

View File

@ -1008,7 +1008,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
</packing>
</child>
<child>
<object class="GtkButtonBox">
<object class="GtkButtonBox" id="main_menu_bttn_box_bar">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="spacing">5</property>
@ -2364,12 +2364,20 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
</packing>
</child>
<child>
<object class="GtkBox">
<object class="GtkBox" id="context_menu">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkBox" id="context_menu">
<object class="GtkExpander">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="expanded">True</property>
<property name="label-fill">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
@ -2439,6 +2447,37 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="position">3</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">False</property>
<property name="label" translatable="yes">Open</property>
<property name="justify">center</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
</packing>
</child>
<child>
<object class="GtkExpander">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-top">5</property>
<property name="margin-bottom">5</property>
<property name="expanded">True</property>
<property name="label-fill">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton">
<property name="label">gtk-new</property>
@ -2447,14 +2486,13 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">New File/Folder...</property>
<property name="margin-top">20</property>
<property name="use-stock">True</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">4</property>
<property name="position">0</property>
</packing>
</child>
<child>
@ -2472,7 +2510,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
<property name="position">1</property>
</packing>
</child>
<child>
@ -2490,7 +2528,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">6</property>
<property name="position">2</property>
</packing>
</child>
<child>
@ -2508,7 +2546,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">7</property>
<property name="position">3</property>
</packing>
</child>
<child>
@ -2526,7 +2564,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">8</property>
<property name="position">4</property>
</packing>
</child>
<child>
@ -2544,9 +2582,40 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">9</property>
<property name="position">5</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">False</property>
<property name="label" translatable="yes">File Actions</property>
<property name="justify">center</property>
<property name="ellipsize">end</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">5</property>
</packing>
</child>
<child>
<object class="GtkExpander">
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="margin-top">5</property>
<property name="margin-bottom">10</property>
<property name="label-fill">True</property>
<child>
<object class="GtkBox">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="orientation">vertical</property>
<child>
<object class="GtkButton" id="restore_from_trash">
<property name="label" translatable="yes">Restore From Trash</property>
@ -2555,13 +2624,12 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Restore From Trash...</property>
<property name="margin-top">20</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">10</property>
<property name="position">0</property>
</packing>
</child>
<child>
@ -2577,7 +2645,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">11</property>
<property name="position">1</property>
</packing>
</child>
<child>
@ -2596,7 +2664,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">12</property>
<property name="position">2</property>
</packing>
</child>
<child>
@ -2614,7 +2682,7 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">13</property>
<property name="position">3</property>
</packing>
</child>
<child>
@ -2633,14 +2701,25 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">14</property>
<property name="position">4</property>
</packing>
</child>
</object>
</child>
<child type="label">
<object class="GtkLabel">
<property name="visible">True</property>
<property name="can-focus">False</property>
<property name="hexpand">False</property>
<property name="label" translatable="yes">Trash</property>
<property name="justify">center</property>
</object>
</child>
</object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">2</property>
<property name="position">6</property>
</packing>
</child>
</object>

View File

@ -8,7 +8,7 @@
"<Control>r"],
"delete_files" : ["Delete",
"<Shift><Control>d"],
"tggl_top_main_menubar" : "<Alt>",
"tggl_top_main_menubar" : "<Alt>h",
"trash_files" : "<Shift><Control>t",
"tear_down" : "<Control>q",
"go_up" : "<Control>Up",