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