Compare commits

...

2 Commits

  1. 23
      README.md
  2. BIN
      images/p5-yt-dlp-gui.png
  3. BIN
      images/yt-dlp-gui.png
  4. 0
      p5-yt-dlp-gui
  5. 153
      py-yt-dlp-gui

23
README.md

@ -50,9 +50,9 @@ GUI-обёртка над `www/yt-dlp` для воспроизведения в
--- ---
__yt-dlp-gui__ __p5-yt-dlp-gui__
![](images/yt-dlp-gui.png) ![](images/p5-yt-dlp-gui.png)
GUI-обёртка над `www/yt-dlp` для воспроизведения видео с GUI-обёртка над `www/yt-dlp` для воспроизведения видео с
[RuTube](https://rutube.ru) и других видеохостингов с помощью [RuTube](https://rutube.ru) и других видеохостингов с помощью
@ -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`).
---

BIN
images/p5-yt-dlp-gui.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

BIN
images/yt-dlp-gui.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

0
yt-dlp-gui → p5-yt-dlp-gui

153
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()
Loading…
Cancel
Save