123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451 |
- #####################################################################
- # #
- # THIS IS A SOURCE CODE FILE FROM A PROGRAM TO INTERACT WITH THE #
- # LBRY PROTOCOL ( lbry.com ). IT WILL USE THE LBRY SDK ( lbrynet ) #
- # FROM THEIR REPOSITORY ( https://github.com/lbryio/lbry-sdk ) #
- # WHICH I GONNA PRESENT TO YOU AS A BINARY. SINCE I DID NOT DEVELOP #
- # IT AND I'M LAZY TO INTEGRATE IN A MORE SMART WAY. THE SOURCE CODE #
- # OF THE SDK IS AVAILABLE IN THE REPOSITORY MENTIONED ABOVE. #
- # #
- # ALL THE CODE IN THIS REPOSITORY INCLUDING THIS FILE IS #
- # (C) J.Y.Amihud and Other Contributors 2021. EXCEPT THE LBRY SDK. #
- # YOU CAN USE THIS FILE AND ANY OTHER FILE IN THIS REPOSITORY UNDER #
- # THE TERMS OF GNU GENERAL PUBLIC LICENSE VERSION 3 OR ANY LATER #
- # VERSION. TO FIND THE FULL TEXT OF THE LICENSE GO TO THE GNU.ORG #
- # WEBSITE AT ( https://www.gnu.org/licenses/gpl-3.0.html ). #
- # #
- # THE LBRY SDK IS UNFORTUNATELY UNDER THE MIT LICENSE. IF YOU ARE #
- # NOT INTENDING TO USE MY CODE AND JUST THE SDK. YOU CAN FIND IT ON #
- # THEIR OFFICIAL REPOSITORY ABOVE. THEIR LICENSE CHOICE DOES NOT #
- # SPREAD ONTO THIS PROJECT. DON'T GET A FALSE ASSUMPTION THAT SINCE #
- # THEY USE A PUSH-OVER LICENSE, I GONNA DO THE SAME. I'M NOT. #
- # #
- # THE LICENSE CHOSEN FOR THIS PROJECT WILL PROTECT THE 4 ESSENTIAL #
- # FREEDOMS OF THE USER FURTHER, BY NOT ALLOWING ANY WHO TO CHANGE #
- # THE LICENSE AT WILL. SO NO PROPRIETARY SOFTWARE DEVELOPER COULD #
- # TAKE THIS CODE AND MAKE THEIR USER-SUBJUGATING SOFTWARE FROM IT. #
- # #
- #####################################################################
- # This file will contain elements used a lot with in the application.
- # They are all built upon GTK, so it's implementable without using these
- # but it may simplify your modification. Since the elements in will be
- # specific to making something like this application easier.
- import os
- import urllib.request
- import threading
- import json
- from subprocess import *
- from gi.repository import Gtk
- from gi.repository import Gio
- from gi.repository import Gdk
- from gi.repository import GLib
- from gi.repository import Pango
- from gi.repository import GdkPixbuf
- from PIL import Image, ImageSequence
- def icon( win, name, f="png"):
- # This function returns an icon of the current setting, or a
- # system default icon if there is no such icon in the set icon theme.
- # For this, of course, we will need to store names of what those
- # icons are called in the system. Not everthing is the same.
-
- # I could name the icons in the custom theme the same way, but
- # this would require using some hilarious names.
-
- system_names = {
- "settings":"application-menu",
- "connect":"network-connect",
- "disconnect":"network-disconnect",
- "loading":"accept_time_event",
- "launch":"practice-start"
- }
-
- try_icon = "icons/"+win.settings["GTK_icon_theme"]+"/"+name+"."+f
-
- if os.path.exists(try_icon):
- return Gtk.Image.new_from_file(try_icon)
- else:
- # Real GTK Spinner for loading ? Why not?
- if name == "loading":
- s = Gtk.Spinner()
- s.set_size_request(64,64)
- s.start()
- return s
- except_icon = Gio.Icon.new_for_string(system_names.get(name, name))
- return Gtk.Image.new_from_gicon(except_icon, Gtk.IconSize.DND)
- def resize_gif(filename, new_file, size):
- # This function will resize a gif
- gif = Image.open(filename)
- layers = ImageSequence.Iterator(gif)
- def rs(l):
- for i in l:
- rsv = i.copy()
- rsv.thumbnail(size, Image.ANTIALIAS)
- yield rsv
- layers = rs(layers)
- # Overwrite the original gif
- f = next(layers)
- f.info = gif.info
- f.save(new_file, save_all=True, append_images=list(layers))
-
- def load(win, calculation_function, render_function, *args, wait=True):
- # This function will load widgets that take time to load.
- # Due to the peculiarities of the GTK main thread. I need
- # to separate the computation and the rendering part of
- # the job into two distingt functions.
- # One will do all the job to get the file or resolve the url
- # or whatever it needs to do, which does not require GTK to
- # be done. The second will be the GTK commands. The rendering,
- # done with in the GTK main thread.
-
- wbox = Gtk.HBox()
- widget = icon(win, "loading", "gif")
- wbox.pack_start(widget, True, False, False)
- widget.loaddo = True
-
- def resolve_widget_thread(widget, wbox, wf, rf, *args):
-
- calculations = wf(*args)
-
- def gtk_schedule(calculations, rf):
- # It seems to be important to edit GTK only
- # in the main thread. This will schedule it.
- new_widget = rf(calculations)
- widget.destroy()
- wbox.pack_start(new_widget, True, True, True)
- wbox.show_all()
- GLib.idle_add(gtk_schedule, calculations, rf)
- def load_event(w,e):
- if w.loaddo:
- load_thread = threading.Thread(target=resolve_widget_thread, args=(widget, wbox, calculation_function, render_function, *args))
- load_thread.setDaemon(True)
- load_thread.start()
- w.loaddo = False
- if wait:
- widget.connect("draw", load_event)
- else:
- load_event(widget, False)
-
- return wbox
- image_cache = "/tmp/FastLBRY_GTK_image_cashe/"
- def image_save_name(url):
- save_as = ""
- good = "qwertyuiopasdfghjklzxcvbnmQWERTYUIOOPASDFGHJKLZXCVBNM_"
- for i in url:
- if i in good:
- save_as = save_as + i
- else:
- save_as = save_as + "_"
- try:
- os.mkdir(image_cache)
- except:
- pass
- save_as = image_cache+save_as
- return save_as
- def clean_image_cache():
- for i in os.listdir(image_cache):
- os.remove(image_cache+i)
- def net_image_calculation( url, size, save_as=False, allow_gif=False):
- ret = ["file", save_as]
- print("save_as", save_as)
- # This is when we want to load a file
-
- if save_as == "FORCELOAD":
- try:
- open(url)
- save_as = url
- except:
- save_as = ""
-
- if not save_as:
- save_as = image_save_name(url)
-
- # This function will load the image in a separate thread.
- try:
- open(save_as) # In case it's already been saved
- except Exception as e:
- print("F", e)
-
- try:
- urllib.request.urlretrieve(url, save_as)
- except Exception as e:
- f = open(save_as, "w")
- f.close()
- if size:
- try:
-
- pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(save_as, size, size)
- ret = ["pixbuf", pixbuf] #Gtk.Image.new_from_pixbuf(pixbuf)
- except Exception as e:
- if "image file format" in str(e):
- try:
- PILImage = Image.open(save_as).convert("RGBA")
- PILImage.save(save_as+".png", "png")
- pixbuf = GdkPixbuf.Pixbuf.new_from_file_at_size(save_as+".png", size, size)
- ret = ["pixbuf", pixbuf]
- except:
- ret = ["file", save_as]
-
- else:
- try:
- os.rename(save_as, save_as+".gif")
- resize_gif(save_as+".gif", save_as+"_2.gif", [size,size])
- ret = ["file", save_as+"_2.gif"] # Gtk.Image.new_from_file(save_as+"_2.gif")
- os.remove(save_as+"_2.gif")
- os.rename(save_as+".gif", save_as)
-
- except Exception as e:
- if allow_gif:
- ret = ["file", save_as] #Gtk.Image.new_from_file(save_as)
-
- else:
- ret = ["file", save_as] #Gtk.Image.new_from_file(save_as)
- return ret
- def net_image_render(calc):
- # This will make the image itself.
- ret = Gtk.Image()
- if calc[0] == "file":
- ret = Gtk.Image.new_from_file(calc[1])
- elif calc[0] == "pixbuf":
- ret = Gtk.Image.new_from_pixbuf(calc[1])
-
- return ret
- def search_item(win, data, channel_load=True):
- # This will generate a little item for the claim_search
- box = Gtk.VBox()
- repost = ""
- if "reposted_claim" in data:
- repost = "Reposted:"
- data = data["reposted_claim"]
- try:
- title = data["value"]["title"]
- except:
- title = data['name']
- def public_resolve(w):
- win.url.set_text(data["canonical_url"])
- win.url.activate()
- namebutton = Gtk.Button()
- namebutton.set_tooltip_text(data["canonical_url"])
- namebutton.connect("clicked", public_resolve)
- namebutton.set_relief(Gtk.ReliefStyle.NONE)
- namebutton_box = Gtk.VBox()
- namebutton.add(namebutton_box)
- if repost:
- namebutton_box.pack_start(Gtk.Label(repost), True,False,0)
-
- try:
- # Trying to get the thumb
- namebutton_thumb = load(win, net_image_calculation, net_image_render, data["value"]["thumbnail"]["url"], 200 , "", False)
- namebutton_thumb.set_size_request(200,200)
- namebutton_box.pack_start(namebutton_thumb, False,False,0)
- except:
- try:
- # Trying to get a thumb by referencing the mimetype
- namebutton_thumb = icon(win,data["value"]["source"]["media_type"].replace("/", "-"))
- namebutton_thumb.set_size_request(200,200)
- namebutton_box.pack_start(namebutton_thumb, False,False,0)
- except:
- namebutton_thumb = icon(win,"none")
- namebutton_thumb.set_size_request(200,200)
- namebutton_box.pack_start(namebutton_thumb, False,False,0)
-
- try:
- title_label = Gtk.Label(title)
- title_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
- title_label.set_line_wrap(True)
- title_label.set_max_width_chars(20)
- namebutton_box.pack_start(title_label, True,False,0)
- except:
- pass
-
- box.pack_start(namebutton, False, False, False)
-
-
- if "signing_channel" in data and (channel_load or repost):
- box.pack_start(go_to_channel(win, data["signing_channel"]), False, False, False)
- ##### DRAGING IT OUT THE WINDOW ####
- def on_drag(widget, drag_context, send, info, time):
- # TODO: swap to FastLBRY HTML instance when ready
- librarian = data["canonical_url"].replace("lbry://", win.settings["librarian_instance"])
- librarian = librarian.replace("#", ":")
- send.set_text(librarian, -1)
-
- namebutton.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
- namebutton.drag_source_add_text_targets()
- namebutton.connect("drag-data-get", on_drag)
-
- return box
-
- def go_to_channel(win, data, resolve=True):
- try:
- try:
- channel_name = data["value"]["title"]
- except:
- channel_name = data["name"]
- try:
- channel_url = data["canonical_url"]
- except:
- channel_url = data["name"]
- try:
- channel_url = channel_url + "#" + data["claim_id"]
- except:
- pass
- channel_button = Gtk.Button()
-
- if resolve:
- def channel_resolve(w):
- win.url.set_text(channel_url)
- win.url.activate()
- try:
- channel_button.set_tooltip_text(data["canonical_url"])
- except:
- channel_button.set_tooltip_text(data["name"])
- channel_button.connect("clicked", channel_resolve)
- channel_button.set_relief(Gtk.ReliefStyle.NONE)
- channel_button_box = Gtk.HBox()
- channel_button.add(channel_button_box)
- # If channel thumbnail exists.
- try:
- channel_thumb = load(win, net_image_calculation, net_image_render, data["value"]["thumbnail"]["url"], 40 , "", False)
- channel_button_box.pack_start(channel_thumb, False,False,False)
- except:
- channel_button_box.pack_start(icon(win, "actor"), False,False,False)
- title_label = Gtk.Label(" "+channel_name+" ")
- title_label.set_line_wrap_mode( Gtk.WrapMode.WORD )
- title_label.set_line_wrap(True)
- title_label.set_max_width_chars(20)
- channel_button_box.pack_start(title_label, False, False, False)
- return channel_button
- except Exception as e:
- print("GO TO CHANNEL:", e)
- return Gtk.Label("[anonymous]")
- def notify(win, text, subtext="", force=False):
- # This function will send a notify send thingy if
- # notifications are set to True
- enabled = win.settings["notifications"]
- if enabled and (( not win.is_active()) or force ):
- Popen(["notify-send",
- "-i", os.getcwd()+"/icon.png",
- "-a", "FastLBRY GTK", text, subtext])
- def select_file(was="", filter=[]):
- # This is a simple file_chooser_dialog.
-
- dialog = Gtk.FileChooserDialog("Choose a file",
- None,
- Gtk.FileChooserAction.OPEN,
- (Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OPEN, Gtk.ResponseType.OK))
- # Filter
- if filter:
- filter_sup = Gtk.FileFilter()
- filter_sup.set_name("Supported files")
- for i in filter:
- filter_sup.add_pattern(i)
- dialog.add_filter(filter_sup)
- filter_any = Gtk.FileFilter()
- filter_any.set_name("All files")
- filter_any.add_pattern("*")
- dialog.add_filter(filter_any)
- ############### PREVIEW CODE ###################
- # TODO:
- # Perhaps more mime-types could be added to it
- # for example .blender could be previewed using
- # blender-thumbnailer.py in each Blender install.
- # Videos by Totem.
-
- preview_image= Gtk.Image()
- dialog.set_preview_widget(preview_image)
- def update_preview(dialog):
- path= dialog.get_preview_filename()
- try:
- pixbuf= GdkPixbuf.Pixbuf.new_from_file(path)
- except Exception:
- dialog.set_preview_widget_active(False)
- else:
- maxwidth, maxheight= 300, 700
- width, height= pixbuf.get_width(), pixbuf.get_height()
- scale= min(maxwidth/width, maxheight/height)
- if scale<1:
- width, height= int(width*scale), int(height*scale)
- pixbuf= pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR)
- preview_image.set_from_pixbuf(pixbuf)
- dialog.set_preview_widget_active(True)
- dialog.connect('update-preview', update_preview)
-
- response = dialog.run()
- if response == Gtk.ResponseType.OK:
- ret = dialog.get_filename()
- dialog.destroy()
- return ret
- else:
- dialog.destroy()
- return was
-
|