Added media downloader project
This commit is contained in:
parent
93cde0000d
commit
2309170ee9
|
@ -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)
|
|
@ -0,0 +1,221 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!-- Generated with glade 3.22.1 -->
|
||||
<interface>
|
||||
<requires lib="gtk+" version="3.20"/>
|
||||
<object class="GtkImage" id="downloadImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-goto-bottom</property>
|
||||
<property name="icon_size">5</property>
|
||||
</object>
|
||||
<object class="GtkListStore" id="downloadTypes">
|
||||
<columns>
|
||||
<!-- column-name Format -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name Video -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name Resolution -->
|
||||
<column type="gchararray"/>
|
||||
<!-- column-name Size -->
|
||||
<column type="gchararray"/>
|
||||
</columns>
|
||||
</object>
|
||||
<object class="GtkImage" id="updateImage">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-connect</property>
|
||||
<property name="icon_size">5</property>
|
||||
</object>
|
||||
<object class="GtkWindow" id="Main_Window">
|
||||
<property name="can_focus">False</property>
|
||||
<property name="title" translatable="yes">Youtube-DL Gui</property>
|
||||
<property name="window_position">center</property>
|
||||
<property name="default_width">650</property>
|
||||
<property name="default_height">420</property>
|
||||
<property name="gravity">center</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="orientation">vertical</property>
|
||||
<child>
|
||||
<object class="GtkBox">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label" translatable="yes">Update</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Update...</property>
|
||||
<property name="image">updateImage</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="updateYoutubeDl" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkEntry" id="urlEntry">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="has_focus">True</property>
|
||||
<property name="has_default">True</property>
|
||||
<property name="placeholder_text" translatable="yes">http://...</property>
|
||||
<signal name="changed" handler="urlChanged" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton">
|
||||
<property name="label">gtk-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="clicked" handler="paste" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkScrolledWindow">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="shadow_type">in</property>
|
||||
<property name="overlay_scrolling">False</property>
|
||||
<child>
|
||||
<object class="GtkTreeView" id="tree">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">True</property>
|
||||
<property name="model">downloadTypes</property>
|
||||
<property name="headers_clickable">False</property>
|
||||
<property name="enable_grid_lines">horizontal</property>
|
||||
<property name="activate_on_single_click">True</property>
|
||||
<signal name="row-activated" handler="setSelected" swapped="no"/>
|
||||
<child internal-child="selection">
|
||||
<object class="GtkTreeSelection"/>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="title" translatable="yes">Format Code</property>
|
||||
<property name="sort_order">descending</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">0</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="title" translatable="yes">Video Type</property>
|
||||
<property name="sort_order">descending</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">1</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="title" translatable="yes">Resolution</property>
|
||||
<property name="sort_order">descending</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">2</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkTreeViewColumn">
|
||||
<property name="title" translatable="yes">Size</property>
|
||||
<property name="sort_order">descending</property>
|
||||
<child>
|
||||
<object class="GtkCellRendererText"/>
|
||||
<attributes>
|
||||
<attribute name="text">3</attribute>
|
||||
</attributes>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkButton" id="downloadButton">
|
||||
<property name="label" translatable="yes">Download</property>
|
||||
<property name="visible">True</property>
|
||||
<property name="sensitive">False</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="focus_on_click">False</property>
|
||||
<property name="receives_default">True</property>
|
||||
<property name="tooltip_text" translatable="yes">Download...</property>
|
||||
<property name="image">downloadImage</property>
|
||||
<property name="always_show_image">True</property>
|
||||
<signal name="clicked" handler="download" swapped="no"/>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">2</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">False</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">0</property>
|
||||
</packing>
|
||||
</child>
|
||||
<child>
|
||||
<object class="GtkImage" id="thumbnailImg">
|
||||
<property name="visible">True</property>
|
||||
<property name="can_focus">False</property>
|
||||
<property name="stock">gtk-missing-image</property>
|
||||
</object>
|
||||
<packing>
|
||||
<property name="expand">True</property>
|
||||
<property name="fill">True</property>
|
||||
<property name="position">1</property>
|
||||
</packing>
|
||||
</child>
|
||||
</object>
|
||||
</child>
|
||||
</object>
|
||||
</interface>
|
|
@ -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()
|
|
@ -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
|
Loading…
Reference in New Issue