123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648 |
- #####################################################################
- # #
- # 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 time
- 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
- from flbry import url
- def icon( win, name, f="png"):
- # This function returns a fitting icon of the current theme,
- # or if not available from another theme on the system.
- # If a custom icon theme is set, it returns the icon from the corresponding folder.
- if Gtk.IconTheme.get_default().has_icon(name) and win.settings["GTK_icon_theme"] == "System Theme":
- return Gtk.Image.new_from_icon_name(name, Gtk.IconSize.DND)
- else:
- # Real GTK Spinner for loading ? Why not?
- if name == "loading":
- s = Gtk.Spinner()
- s.set_size_request(32,32)
- s.start()
- return s
-
- return Gtk.Image.new_from_file("icons/"+win.settings["GTK_icon_theme"]+"/"+name+"."+f)
- 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_1234567890"
- 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]
- # 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:
- pass
-
- 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
- # Converts seconds to time format (H:MM:SS or M:SS)
- def seconds_to_time(seconds=0):
- m, s = divmod(seconds, 60)
- h, m = divmod(m, 60)
- if h:
- return '{:d}:{:02d}:{:02d}'.format(h, m, s)
- else:
- return '{:d}:{:02d}'.format(m, s)
- def search_item(win, data, channel_load=True, upcoming_streams=True):
- # This will generate a little item for the claim_search
-
- box = Gtk.VBox()
- repost = False
- if "reposted_claim" in data:
- repost = True
- data = data["reposted_claim"]
- try:
- title = data["value"]["title"]
- except:
- title = data['name']
- if upcoming_streams and int(data.get("value",[]).get("release_time", 0)) > time.time():
- title = "[ UPCOMING "+time.ctime( int(data.get("value",[]).get("release_time", 0)))+" ] \n" + title
-
- try:
- link = data["canonical_url"]
- except:
- link = data["permanent_url"]
-
- def public_resolve(w):
- win.url.set_text(link)
- win.url.activate()
- def new_tab(w, e):
- if e.get_button()[1] == 2:
- print("MMB")
- win.resolve_tab = "new_tab"
- win.url.set_text(link)
- win.url.activate()
- namebutton = Gtk.Button()
- namebutton.set_tooltip_text(link)
- namebutton.connect("clicked", public_resolve)
- namebutton.connect("button-press-event", new_tab)
- namebutton.set_relief(Gtk.ReliefStyle.NONE)
- namebutton_box = Gtk.VBox()
- namebutton.add(namebutton_box)
- 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)
- 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)
- except:
- namebutton_thumb = icon(win,"none")
- namebutton_thumb.set_size_request(200,200)
- # For overlay info on top of thumbnail image
- thumbnail_overlay = Gtk.Overlay()
- overlay_box = Gtk.HBox(spacing=5)
- thumbnail_overlay.add(namebutton_thumb)
- thumbnail_overlay.add_overlay(overlay_box)
- thumbnail_overlay.set_overlay_pass_through(overlay_box, True)
- overlay_box.set_margin_end(25)
- overlay_box.set_margin_bottom(48)
- overlay_box.set_halign(Gtk.Align.END)
- overlay_box.set_valign(Gtk.Align.END)
- # Prepare info for overlay
- overlay_items_count = 0
- seconds_length = data.get("value", {}) \
- .get("video", {}) \
- .get("duration", "0")
- downloaded_file = url.get_downloaded_file(data.get("claim_id", ""))
- # Show time
- try:
- if seconds_length != "0":
- overlay_items_count += 1
- time_label = Gtk.Label()
- # For some reason setting text color with CssProvider does not
- # work when loading thumbnail list for a second time. So we use
- # set_markup for text color.
- time_label.set_markup("<span color=\"#EFEFEF\" fgalpha=\"70%\">"+seconds_to_time(seconds_length)+"</span>")
- overlay_box.pack_start(time_label, False, False, 0)
- except:
- pass
- # Show repost indicator
- try:
- if repost:
- overlay_items_count += 1
- repost_icon = Gtk.Image.new_from_file("icons/repost.svg")
- repost_icon.set_size_request(16, 16)
- overlay_box.pack_start(repost_icon, False, False, 0)
- except:
- pass
- # Show downloaded indicator
- try:
- if downloaded_file:
- overlay_items_count += 1
- downloaded_icon = Gtk.Image.new_from_file("icons/downloaded.svg")
- downloaded_icon.set_size_request(16, 16)
- overlay_box.pack_start(downloaded_icon, False, False, 0)
- except:
- pass
- # Finishing up with thumbnail overlay
- thumbnail_overlay.show_all()
- namebutton_box.pack_start(thumbnail_overlay, False, False, 0)
- if overlay_items_count > 0:
- # Add "thumbnail_overlay" class only when there is something inside
- # the box. Meaning don't add it when empty because padding set on
- # CssProvider makes the empty box show, which is unnecessary.
- overlay_box.get_style_context().add_class("thumbnail_overlay")
-
- 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 = link.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)
- def new_tab(w, e):
- if e.get_button()[1] == 2:
- print("MMB")
- win.resolve_tab = "new_tab"
- win.url.set_text(channel_url)
- win.url.activate()
- channel_button.connect("button-press-event", new_tab)
-
- 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, "system-users"), 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)
- def on_drag(widget, drag_context, send, info, time):
- # TODO: swap to FastLBRY HTML instance when ready
- librarian = channel_url.replace("lbry://", win.settings["librarian_instance"])
- librarian = librarian.replace("#", ":")
- send.set_text(librarian, -1)
-
- channel_button.drag_source_set(Gdk.ModifierType.BUTTON1_MASK, [], Gdk.DragAction.COPY)
- channel_button.drag_source_add_text_targets()
- channel_button.connect("drag-data-get", on_drag)
-
- 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
-
- def tags_editor(win, data, return_edit_functions=False):
-
-
- tagscont = Gtk.HBox()
-
-
- tagscrl = Gtk.ScrolledWindow()
- tagscrl.set_size_request(40,40)
- tagscont.pack_start(tagscrl, True, True, 0)
- tagsbox = Gtk.HBox()
- tagscrl.add_with_viewport(tagsbox)
- def add_tag(tag):
- if not tag:
- return
-
- if tag not in data:
- data.append(tag)
- tagb = Gtk.HBox()
- tagb.pack_start(Gtk.Label(" "+tag+" "), False, False, 0)
- def kill(w):
- tagb.destroy()
- data.remove(tag)
- tagk = Gtk.Button()
- tagk.connect("clicked", kill)
- tagk.set_relief(Gtk.ReliefStyle.NONE)
- tagk.add(icon(win, "edit-delete"))
- tagb.pack_start(tagk, False, False, 0)
- tagb.pack_start(Gtk.VSeparator(), False, False, 5)
- tagsbox.pack_start(tagb, False, False, 0)
- tagsbox.show_all()
- # Scroll to the last
- def later():
- time.sleep(0.1)
- def now():
- a = tagscrl.get_hadjustment()
- a.set_value(a.get_upper())
- GLib.idle_add(now)
- load_thread = threading.Thread(target=later)
- load_thread.start()
- # The threading is needed, since we want to wait
- # while GTK will update the UI and only then move
- # the adjustent. Becuase else, it will move to the
- # last previous, not to the last last.
-
- addt = Gtk.Button()
- addt.set_relief(Gtk.ReliefStyle.NONE)
- addt.add(icon(win, "list-add"))
- tagscont.pack_end(addt, False, False, 0)
- def on_entry(w):
- add_tag(tagentry.get_text())
- tagentry.set_text("")
- tagentry = Gtk.Entry()
- tagentry.connect("activate", on_entry)
- addt.connect("clicked", on_entry)
- tagscont.pack_end(tagentry, False, False, False)
-
-
- for tag in data:
- add_tag(tag)
- if not return_edit_functions:
- return tagscont
- else:
- return tagscont, tagsbox, add_tag
- def password_entry(win):
- # This function will create a basic entry for passwords
- # with a button to reveal text.
- box = Gtk.HBox()
-
- entry = Gtk.Entry()
- entry.set_visibility(False)
- def vis(w, e):
- e.set_visibility(w.get_active())
- button = Gtk.ToggleButton()
- button.set_relief(Gtk.ReliefStyle.NONE)
- button.add(icon(win, "video-display"))
- button.connect("clicked", vis, entry)
- box.pack_start(entry, 1,1,2)
- box.pack_end(button, 0,0,1)
- return box, entry
- # Show a simple message box with only OK button
- def simple_message_box(primary_text, secondary_text):
- dialog = Gtk.MessageDialog(
- transient_for=None,
- flags=0,
- message_type=Gtk.MessageType.INFO,
- buttons=Gtk.ButtonsType.OK,
- text=primary_text,
- )
- dialog.format_secondary_text(secondary_text)
- dialog.run()
- dialog.destroy()
|