plugin work #7

Merged
itdominator merged 2 commits from develop into master 2022-09-05 06:09:40 +00:00
16 changed files with 1174 additions and 270 deletions

View File

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

View File

@ -26,7 +26,7 @@ def daemon_threaded(fn):
class Plugin: class Plugin:
def __init__(self): 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 # where self.name should not be needed for message comms
self.path = os.path.dirname(os.path.realpath(__file__)) self.path = os.path.dirname(os.path.realpath(__file__))
self._GLADE_FILE = f"{self.path}/favorites.glade" 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 from core.controller import Controller
class App_Launch_Exception(Exception):
...
class Controller_Start_Exceptio(Exception):
...
class Application(IPCServer): class Application(IPCServer):
""" Create Settings and Controller classes. Bind signal to Builder. Inherit from Builtins to bind global methods and classes. """ """ 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}" message = f"FILE|{args.new_tab}"
self.send_ipc_message(message) 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() settings = Settings()
@ -40,7 +46,7 @@ class Application(IPCServer):
controller = Controller(args, unknownargs, settings) controller = Controller(args, unknownargs, settings)
if not controller: 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. # Gets the methods from the classes and sets to handler.
# Then, builder connects to any signals it needs. # Then, builder connects to any signals it needs.

View File

@ -50,7 +50,8 @@ class ManifestProcessor:
if "ui_target" in keys: if "ui_target" in keys:
if requests["ui_target"] in [ 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" "context_menu", "window_1", "window_2", "window_3", "window_4"
]: ]:
if requests["ui_target"] == "other": if requests["ui_target"] == "other":

View File

@ -65,7 +65,9 @@ class Plugins:
def load_plugin_module(self, path, folder, target): def load_plugin_module(self, path, folder, target):
os.chdir(path) 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) spec = importlib.util.spec_from_file_location(folder, target)
module = importlib.util.module_from_spec(spec) module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module) 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> </packing>
</child> </child>
<child> <child>
<object class="GtkButtonBox"> <object class="GtkButtonBox" id="main_menu_bttn_box_bar">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="spacing">5</property> <property name="spacing">5</property>
@ -2364,283 +2364,362 @@ SolarFM is developed on Atom, git, and using Python 3+ with Gtk GObject introspe
</packing> </packing>
</child> </child>
<child> <child>
<object class="GtkBox"> <object class="GtkBox" id="context_menu">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">False</property>
<property name="orientation">vertical</property> <property name="orientation">vertical</property>
<child> <child>
<object class="GtkBox" id="context_menu"> <object class="GtkExpander">
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">False</property> <property name="can-focus">True</property>
<property name="orientation">vertical</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> <child>
<object class="GtkButton"> <object class="GtkBox">
<property name="label">gtk-open</property>
<property name="name">open</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="orientation">vertical</property>
<property name="tooltip-text" translatable="yes">Open...</property> <child>
<property name="use-stock">True</property> <object class="GtkButton">
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/> <property name="label">gtk-open</property>
<property name="name">open</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Open...</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">0</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Open With</property>
<property name="name">open_with</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">open_with_img</property>
<property name="always-show-image">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">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-execute</property>
<property name="name">execute</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</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">2</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Execute in Terminal</property>
<property name="name">execute_in_terminal</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">exec_in_term_img</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">3</property>
</packing>
</child>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">0</property>
</packing>
</child> </child>
<child> <child type="label">
<object class="GtkButton"> <object class="GtkLabel">
<property name="label" translatable="yes">Open With</property>
<property name="name">open_with</property>
<property name="visible">True</property> <property name="visible">True</property>
<property name="can-focus">True</property> <property name="can-focus">False</property>
<property name="receives-default">True</property> <property name="hexpand">False</property>
<property name="image">open_with_img</property> <property name="label" translatable="yes">Open</property>
<property name="always-show-image">True</property> <property name="justify">center</property>
<signal name="button-release-event" handler="do_action_from_menu_controls" swapped="no"/>
</object> </object>
<packing>
<property name="expand">False</property>
<property name="fill">True</property>
<property name="position">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-execute</property>
<property name="name">execute</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</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">2</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Execute in Terminal</property>
<property name="name">execute_in_terminal</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="image">exec_in_term_img</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">3</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-new</property>
<property name="name">create</property>
<property name="visible">True</property>
<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>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Rename</property>
<property name="name">rename</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Rename...</property>
<property name="image">rename_img2</property>
<property name="always-show-image">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">5</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-cut</property>
<property name="name">cut</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Cut...</property>
<property name="use-stock">True</property>
<property name="always-show-image">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">6</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-copy</property>
<property name="name">copy</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Copy...</property>
<property name="use-stock">True</property>
<property name="always-show-image">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">7</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-paste</property>
<property name="name">paste</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Paste...</property>
<property name="use-stock">True</property>
<property name="always-show-image">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">8</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Archive</property>
<property name="name">archive</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Archive...</property>
<property name="image">archive_img</property>
<property name="always-show-image">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">9</property>
</packing>
</child>
<child>
<object class="GtkButton" id="restore_from_trash">
<property name="label" translatable="yes">Restore From Trash</property>
<property name="name">restore_from_trash</property>
<property name="visible">True</property>
<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>
</packing>
</child>
<child>
<object class="GtkButton" id="empty_trash">
<property name="label" translatable="yes">Empty Trash</property>
<property name="name">empty_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Empty Trash...</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">11</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Trash</property>
<property name="name">trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Move to Trash...</property>
<property name="margin-top">20</property>
<property name="image">trash_img</property>
<property name="always-show-image">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">12</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Go To Trash</property>
<property name="name">go_to_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Go To Trash...</property>
<property name="image">trash_img2</property>
<property name="always-show-image">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">13</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-delete</property>
<property name="name">delete</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Delete...</property>
<property name="margin-top">20</property>
<property name="use-stock">True</property>
<property name="always-show-image">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">14</property>
</packing>
</child> </child>
</object> </object>
<packing> <packing>
<property name="expand">False</property> <property name="expand">False</property>
<property name="fill">True</property> <property name="fill">True</property>
<property name="position">2</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>
<property name="name">create</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">New File/Folder...</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">0</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Rename</property>
<property name="name">rename</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Rename...</property>
<property name="image">rename_img2</property>
<property name="always-show-image">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">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-cut</property>
<property name="name">cut</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Cut...</property>
<property name="use-stock">True</property>
<property name="always-show-image">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">2</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-copy</property>
<property name="name">copy</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Copy...</property>
<property name="use-stock">True</property>
<property name="always-show-image">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">3</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-paste</property>
<property name="name">paste</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Paste...</property>
<property name="use-stock">True</property>
<property name="always-show-image">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>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Archive</property>
<property name="name">archive</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Archive...</property>
<property name="image">archive_img</property>
<property name="always-show-image">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">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>
<property name="name">restore_from_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Restore From Trash...</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">0</property>
</packing>
</child>
<child>
<object class="GtkButton" id="empty_trash">
<property name="label" translatable="yes">Empty Trash</property>
<property name="name">empty_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Empty Trash...</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">1</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Trash</property>
<property name="name">trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Move to Trash...</property>
<property name="margin-top">20</property>
<property name="image">trash_img</property>
<property name="always-show-image">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">2</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label" translatable="yes">Go To Trash</property>
<property name="name">go_to_trash</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Go To Trash...</property>
<property name="image">trash_img2</property>
<property name="always-show-image">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">3</property>
</packing>
</child>
<child>
<object class="GtkButton">
<property name="label">gtk-delete</property>
<property name="name">delete</property>
<property name="visible">True</property>
<property name="can-focus">True</property>
<property name="receives-default">True</property>
<property name="tooltip-text" translatable="yes">Delete...</property>
<property name="margin-top">20</property>
<property name="use-stock">True</property>
<property name="always-show-image">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>
</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">6</property>
</packing> </packing>
</child> </child>
</object> </object>

View File

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