From 2309170ee95ceefb8180f3dd924eecbfb760c38d Mon Sep 17 00:00:00 2001 From: itdominator <1itdominator@gmail.com> Date: Mon, 22 Jan 2024 22:14:22 -0600 Subject: [PATCH] Added media downloader project --- .../gtk/Media Downloader/media-downloader.py | 49 ++++ .../resources/Main_Window.glade | 221 ++++++++++++++++++ .../Media Downloader/resources/stylesheet.css | 0 .../signal_classes/__init__.py | 0 .../signal_classes/controller.py | 128 ++++++++++ .../gtk/Media Downloader/utils/__init__.py | 0 .../gtk/Media Downloader/utils/settings.py | 55 +++++ 7 files changed, 453 insertions(+) create mode 100755 Python Projects/gtk/Media Downloader/media-downloader.py create mode 100644 Python Projects/gtk/Media Downloader/resources/Main_Window.glade create mode 100644 Python Projects/gtk/Media Downloader/resources/stylesheet.css create mode 100644 Python Projects/gtk/Media Downloader/signal_classes/__init__.py create mode 100644 Python Projects/gtk/Media Downloader/signal_classes/controller.py create mode 100644 Python Projects/gtk/Media Downloader/utils/__init__.py create mode 100644 Python Projects/gtk/Media Downloader/utils/settings.py diff --git a/Python Projects/gtk/Media Downloader/media-downloader.py b/Python Projects/gtk/Media Downloader/media-downloader.py new file mode 100755 index 0000000..932a06d --- /dev/null +++ b/Python Projects/gtk/Media Downloader/media-downloader.py @@ -0,0 +1,49 @@ +#!/usr/bin/python3 + +# Gtk imports +import gi, faulthandler, signal +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk +from gi.repository import Gdk +from gi.repository import GLib + +# Python imports +import inspect + +from setproctitle import setproctitle + +# Application imports +from utils.settings import Settings +from signal_classes.controller import Controller + + +class Main: + def __init__(self): + setproctitle('Media Downloader') + GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit) + faulthandler.enable() # For better debug info + + settings = Settings() + builder = settings.returnBuilder() + + # Gets the methods from the classes and sets to handler. + # Then, builder connects to any signals it needs. + classes = [Controller(settings)] + + handlers = {} + for c in classes: + methods = inspect.getmembers(c, predicate=inspect.ismethod) + handlers.update(methods) + + builder.connect_signals(handlers) + window = settings.createWindow() + window.show() + + +if __name__ == "__main__": + try: + main = Main() + Gtk.main() + except Exception as e: + print(e) diff --git a/Python Projects/gtk/Media Downloader/resources/Main_Window.glade b/Python Projects/gtk/Media Downloader/resources/Main_Window.glade new file mode 100644 index 0000000..7e75aa0 --- /dev/null +++ b/Python Projects/gtk/Media Downloader/resources/Main_Window.glade @@ -0,0 +1,221 @@ + + + + + + True + False + gtk-goto-bottom + 5 + + + + + + + + + + + + + + + True + False + gtk-connect + 5 + + + False + Youtube-DL Gui + center + 650 + 420 + center + + + True + False + + + True + False + vertical + + + True + False + + + Update + True + True + True + Update... + updateImage + True + + + + False + True + 0 + + + + + True + True + True + True + http://... + + + + True + True + 1 + + + + + gtk-paste + True + True + True + Paste... + True + True + + + + False + True + 2 + + + + + False + True + 0 + + + + + True + True + in + False + + + True + True + downloadTypes + False + horizontal + True + + + + + + + Format Code + descending + + + + 0 + + + + + + + Video Type + descending + + + + 1 + + + + + + + Resolution + descending + + + + 2 + + + + + + + Size + descending + + + + 3 + + + + + + + + + True + True + 1 + + + + + Download + True + False + False + False + True + Download... + downloadImage + True + + + + False + True + 2 + + + + + False + True + 0 + + + + + True + False + gtk-missing-image + + + True + True + 1 + + + + + + diff --git a/Python Projects/gtk/Media Downloader/resources/stylesheet.css b/Python Projects/gtk/Media Downloader/resources/stylesheet.css new file mode 100644 index 0000000..e69de29 diff --git a/Python Projects/gtk/Media Downloader/signal_classes/__init__.py b/Python Projects/gtk/Media Downloader/signal_classes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Python Projects/gtk/Media Downloader/signal_classes/controller.py b/Python Projects/gtk/Media Downloader/signal_classes/controller.py new file mode 100644 index 0000000..c0f9dde --- /dev/null +++ b/Python Projects/gtk/Media Downloader/signal_classes/controller.py @@ -0,0 +1,128 @@ +# Gtk imports +import gi, faulthandler, signal +gi.require_version('Gtk', '3.0') + +from gi.repository import Gtk +from gi.repository import GLib +from gi.repository.GdkPixbuf import Pixbuf + +# Python imports +import threading, subprocess, os, requests + +from setproctitle import setproctitle + +# Application imports + + +def threaded(fn): + def wrapper(*args, **kwargs): + threading.Thread(target=fn, args=args, kwargs=kwargs).start() + + return wrapper + + +class Controller: + def __init__(self, settings): + setproctitle('Media Downloader') + GLib.unix_signal_add(GLib.PRIORITY_DEFAULT, signal.SIGINT, Gtk.main_quit) + faulthandler.enable() # For better debug info + + self.settings = settings + self.builder = self.settings.returnBuilder() + self.downloadTypes = self.builder.get_object("downloadTypes") + self.urlEntry = self.builder.get_object("urlEntry") + self.downloadButton = self.builder.get_object("downloadButton") + self.thumbnail = self.builder.get_object("thumbnailImg") + + self.selected = None + + + def updateYoutubeDl(self, widget): + pass + + def paste(self, widget): + entry = self.getClipboardData() + self.urlEntry.set_text(entry) + + @threaded + def urlChanged(self, widget): # This also gets called by paste + entry = widget.get_text().strip() + size = len(entry) + thumbUrl = "https://img.youtube.com/vi/" + entry[(size - 11): size] + "/hqdefault.jpg" + + self.setThumbnail(thumbUrl) + self.downloadButton.set_sensitive(False) + self.downloadTypes.clear() + if entry: + if len(entry) in [28, 43]: + if "youtu.be" in entry or "watch?v=" in entry: + self.showDownloadTypes() + + + def showDownloadTypes(self): + lines = self.getFormatData().split("\n") + + # Remove pre format strings + i = 0 + while i <= 3: + del lines[0] + i += 1 + + for line in lines: + parts = line.split() + + while len(parts) != 4: + parts.append("NO DATA") + + if parts[3][len(parts[3]) - 1] == ",": + parts[3] = parts[3][0:len(parts[3]) - 1] + + self.downloadTypes.append(parts) + + + @threaded + def download(self, widget): + url = self.urlEntry.get_text() + dlComm = ["youtube-dl", "--no-playlist", "-o", "~/Downloads/%(title)s.%(ext)s", + "-f", self.selected, url] + proc = subprocess.Popen(dlComm, stdout=subprocess.PIPE) # Gets formats + + while True: + line = proc.stdout.readline() + if not line: break + print(line.decode("utf-8").rstrip()) + + def setSelected(self, widget, row, col): + model = widget.get_model() + self.selected = model[row][0] + self.downloadButton.set_sensitive(True) + + @threaded + def setThumbnail(self, url): + fname = "/tmp/thumbnail-hqdefault.jpg" + r = requests.get(url, allow_redirects=True) + + open(fname, 'wb').write(r.content) + self.thumbnail.set_from_pixbuf(Pixbuf.new_from_file(fname)) + + def getFormatData(self): + url = self.urlEntry.get_text() + dlComm = ["youtube-dl", "-F", url] + awkComm = ["awk", "{ print $1, $2, $3, $11 }"] + + proc = subprocess.Popen(dlComm, stdout=subprocess.PIPE) # Gets formats + proc.wait() + data = subprocess.check_output(awkComm, stdin=proc.stdout) # "Piping" to awk to get columns + return data.decode("utf-8").strip() + + def getClipboardData(self): + proc = subprocess.Popen(['xclip','-selection', 'clipboard', '-o'], stdout=subprocess.PIPE) + retcode = proc.wait() + data = proc.stdout.read() + return data.decode("utf-8").strip() + + def setClipboardData(self, data): + proc = subprocess.Popen(['xclip','-selection','clipboard'], stdin=subprocess.PIPE) + proc.stdin.write(data) + proc.stdin.close() + retcode = proc.wait() \ No newline at end of file diff --git a/Python Projects/gtk/Media Downloader/utils/__init__.py b/Python Projects/gtk/Media Downloader/utils/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/Python Projects/gtk/Media Downloader/utils/settings.py b/Python Projects/gtk/Media Downloader/utils/settings.py new file mode 100644 index 0000000..8b4f198 --- /dev/null +++ b/Python Projects/gtk/Media Downloader/utils/settings.py @@ -0,0 +1,55 @@ +# Gtk imports +import gi, cairo +gi.require_version('Gtk', '3.0') +gi.require_version('Gdk', '3.0') + +from gi.repository import Gtk as gtk +from gi.repository import Gdk as gdk + +# Python imports +import os + +# Application imports + + +class Settings: + def __init__(self): + self.scrptPth = os.path.dirname(os.path.realpath(__file__)) + "/" + self.builder = gtk.Builder() + self.builder.add_from_file(self.scrptPth + "../resources/Main_Window.glade") + + + def createWindow(self): + # Get window and connect signals + window = self.builder.get_object("Main_Window") + window.connect("delete-event", gtk.main_quit) + self.setWindowData(window) + return window + + def setWindowData(self, window): + screen = window.get_screen() + visual = screen.get_rgba_visual() + if visual != None and screen.is_composited(): + window.set_visual(visual) + + # bind css file + cssProvider = gtk.CssProvider() + cssProvider.load_from_path(self.scrptPth + '../resources/stylesheet.css') + screen = gdk.Screen.get_default() + styleContext = gtk.StyleContext() + styleContext.add_provider_for_screen(screen, cssProvider, gtk.STYLE_PROVIDER_PRIORITY_USER) + + # window.set_app_paintable(True) + + def getMonitorData(self, screen): + monitors = [] + for m in range(screen.get_n_monitors()): + monitors.append(screen.get_monitor_geometry(m)) + + for monitor in monitors: + print(str(monitor.width) + "x" + str(monitor.height) + "+" + str(monitor.x) + "+" + str(monitor.y)) + + return monitors + + + def returnBuilder(self): return self.builder