diff --git a/README.md b/README.md index dcf22f0..d2bc0e5 100644 --- a/README.md +++ b/README.md @@ -70,3 +70,22 @@ GUI-обёртка над `www/yt-dlp` для воспроизведения в --- +__py-yt-dlp-gui__ + +![](images/py-yt-dlp-gui.png) + +GUI-обёртка над `www/yt-dlp` для воспроизведения видео с +[RuTube](https://rutube.ru) и других видеохостингов с помощью +[mpv](https://mpv.io/). + +Этот вариант написан на [Python3](https://www.python.org/) и +[Gtk3](https://docs.gtk.org/gtk3/). + +Для работы скрипта требуются следующие компоненты: +- `python` (`lang/python311`); +- `py-gobject3` (`devel/py-gobject3`); +- `yt-dlp` (`www/py-yt-dlp`); +- `mpv` (`multimedia/mpv`). + +--- + diff --git a/py-yt-dlp-gui b/py-yt-dlp-gui new file mode 100755 index 0000000..7524440 --- /dev/null +++ b/py-yt-dlp-gui @@ -0,0 +1,153 @@ +#!/usr/bin/env python3 +# coding: utf-8 +# vim:et:sta:sts=4:sw=4:ts=8:tw=79: + +import gi + +gi.require_version("Gtk", "3.0") +from gi.repository import Gtk + +import shlex, subprocess +import yt_dlp + +class wnd(Gtk.Window): + def __init__(self): + super().__init__( + border_width = 20, + default_height = 240, + default_width = 480, + icon_name = "youtube", + resizable = False, + title = "yt-dlp GUI", + window_position = Gtk.WindowPosition.CENTER, + ) + + self.controls = { + 'url' : { 'e':Gtk.Entry(hexpand = True), + 'l':Gtk.Label(label = "Video URL:", xalign = 0), + 'w':2 }, + 'title' : { 'e':Gtk.Entry(editable = False, hexpand = True), + 'l':Gtk.Label(label = "Title:", xalign = 0), + 'w':3 }, + 'resolution' : { 'e':Gtk.ComboBoxText(), + 'l':Gtk.Label(label = "Resolution:", xalign = 0), + 'w':1 }, + 'scale' : { 'e':Gtk.ComboBoxText(), + 'l':Gtk.Label(label = "Scale:", xalign = 0), + 'w':1 } + } + + self.chk = [ + { 'e':Gtk.CheckButton(label = "On-Top"), + 'opt':' --ontop' }, + { 'e':Gtk.CheckButton(label = "Without borders"), + 'opt':' --no-border' }, + { 'e':Gtk.CheckButton(label = "On all workspaces"), + 'opt':' --on-all-workspaces' } + ] + + self.url = "" + + vbox = Gtk.Box(orientation = Gtk.Orientation.VERTICAL, spacing = 8) + self.add(vbox) + + g_main = Gtk.Grid(column_spacing = 8, row_spacing = 8, hexpand = True) + vbox.pack_start(g_main, True, True, 0) + + g_buttons = Gtk.Grid(column_spacing = 8, row_spacing = 8, + halign = Gtk.Align.END, valign = Gtk.Align.END) + vbox.pack_start(g_buttons, True, True, 0) + + for k in self.controls.keys(): + g_main.attach_next_to(self.controls[k]['l'], None, + Gtk.PositionType.BOTTOM, 1, 1) + g_main.attach_next_to(self.controls[k]['e'], + self.controls[k]['l'], + Gtk.PositionType.RIGHT, + self.controls[k]['w'], 1) + + b_get_info = Gtk.Button(image = Gtk.Image.new_from_icon_name( + "gtk-find", Gtk.IconSize.BUTTON)) + g_main.attach_next_to(b_get_info, self.controls['url']['e'], + Gtk.PositionType.RIGHT, 1, 1) + + g_checkboxes = Gtk.Grid(column_spacing = 8, row_spacing = 8, + hexpand = True) + g_main.attach(g_checkboxes, 2, 2, 1, 4) + + for c in self.chk: + g_checkboxes.attach_next_to(c['e'], None, + Gtk.PositionType.BOTTOM, 1, 1) + + b_ok = Gtk.Button(label = "Ok", + image = Gtk.Image.new_from_icon_name( + "gtk-ok", Gtk.IconSize.BUTTON)) + b_cancel = Gtk.Button(label = "Cancel", + image = Gtk.Image.new_from_icon_name( + "gtk-cancel", Gtk.IconSize.BUTTON)) + + g_buttons.attach(b_ok, 0, 0, 1, 1) + g_buttons.attach(b_cancel, 1, 0, 1, 1) + + for s in ["x0.5", "x1", "x2"]: + self.controls['scale']['e'].append_text(s) + + self.controls['scale']['e'].set_active(1) + + b_cancel.connect("clicked", self.on_cancel_clicked) + b_ok.connect("clicked", self.on_ok_clicked) + b_get_info.connect("clicked", self.get_info) + + def on_cancel_clicked(self, button): + self.destroy() + + def on_ok_clicked(self, button): + cmd = "mpv --no-terminal" + + for c in self.chk: + if c['e'].get_active(): + cmd += c['opt'] + + cmd += " --window-scale=" + cmd += self.controls['scale']['e'].get_active_text()[1:] + cmd += " --ytdl-format=\"bv*[height<=" + cmd += self.controls['resolution']['e'].get_active_text()[0:-1] + cmd += "]+ba/b[height<=" + cmd += self.controls['resolution']['e'].get_active_text()[0:-1] + cmd += "] / wv*+ba/w\" " + cmd += self.url + + args = shlex.split(cmd) + + p = subprocess.Popen(args, + stdin = subprocess.DEVNULL, + stdout = subprocess.DEVNULL, + stderr = subprocess.DEVNULL) + + self.destroy() + + def get_info(self, button): + self.url = self.controls['url']['e'].get_text() + + if self.url: + ydl_opts = { 'quiet': True, 'no-warnings': True } + with yt_dlp.YoutubeDL(ydl_opts) as ydl: + info = ydl.extract_info(self.url, download = False) + self.controls['title']['e'].set_text(info['title']) + + h = [] + for f in info['formats']: + if f['vcodec'] != "none" and f['height'] not in h: + h.append(f['height']) + self.controls['resolution']['e'].append_text( + "{}p".format(f['height'])) + + self.controls['resolution']['e'].set_active(0) + else: + self.controls['title']['e'].set_text("Error: Empty url.") + +win = wnd() +win.connect("destroy", Gtk.main_quit) +win.show_all() +Gtk.main() +