diff --git a/app/tools/yt.py b/app/tools/yt.py
index e80e6f6d..e3f40256 100644
--- a/app/tools/yt.py
+++ b/app/tools/yt.py
@@ -13,6 +13,8 @@ _YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{23,})
_YT_VIDEO_PATTERN = re.compile(r"https://r\d+---sn-[\w]{10}-[\w]{3,5}.googlevideo.com/videoplayback?.*")
_HEADERS = {"User-Agent": "Mozilla/5.0"}
+Quality = {137: "1080p", 136: "720p", 135: "480p", 134: "360p", 133: "240p", 160: "144p", 0: "0p"}
+
class YouTube:
""" Helper class for working with YouTube service. """
@@ -39,7 +41,7 @@ class YouTube:
def get_yt_link(video_id):
""" Getting link to YouTube video by id.
- returns tuple from the video link and title
+ returns tuple from the video links dict and title
"""
req = Request("https://youtube.com/get_video_info?video_id={}".format(video_id), headers=_HEADERS)
with urllib.request.urlopen(req, timeout=2) as resp:
@@ -56,29 +58,28 @@ class YouTube:
det = resp.get("videoDetails", None)
title = det.get("title", None) if det else None
streaming_data = resp.get("streamingData", None)
- fmts = streaming_data.get("formats", None) if streaming_data else None
+ fmts = streaming_data.get("adaptiveFormats", None) if streaming_data else None
if fmts:
- url = None
- for f in fmts:
- # TODO implement the choice of quality.
- url = f.get("url", None)
- break
+ urls = {Quality[i["itag"]]: i["url"] for i in
+ filter(lambda i: i.get("itag", -1) in Quality, fmts)}
- if url and title:
- return url, title.replace("+", " ")
+ if urls and title:
+ return urls, title.replace("+", " ")
stream_map = out.get("url_encoded_fmt_stream_map", None)
if stream_map:
s_map = {k: v for k, sep, v in (str(d).partition("=") for d in stream_map.split("&"))}
url, title = s_map.get("url", None), out.get("title", None)
- return urllib.request.unquote(url) if url else "", title.replace("+", " ") if title else ""
+ url, title = urllib.request.unquote(url) if url else "", title.replace("+", " ") if title else ""
+ if url and title:
+ return {Quality[0]: url}, title.replace("+", " ")
rsn = out.get("reason", None)
rsn = rsn.replace("+", " ") if rsn else ""
log("{}: Getting link to video with id {} filed! Cause: {}".format(__class__.__name__, video_id, rsn))
- return "", rsn
+ return None, rsn
class PlayListParser(HTMLParser):
diff --git a/app/ui/iptv.glade b/app/ui/iptv.glade
index afdf9588..83c71ea4 100644
--- a/app/ui/iptv.glade
+++ b/app/ui/iptv.glade
@@ -1228,6 +1228,35 @@ Author: Dmitriy Yefremov
+
diff --git a/app/ui/iptv.py b/app/ui/iptv.py
index f2aa564b..8d612ab8 100644
--- a/app/ui/iptv.py
+++ b/app/ui/iptv.py
@@ -14,7 +14,7 @@ from app.commons import run_idle, run_task, log
from app.eparser.ecommons import BqServiceType, Service
from app.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT
from app.properties import Profile
-from app.tools.yt import YouTube, PlayListParser
+from app.tools.yt import YouTube, PlayListParser, Quality
from .dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
from .main_helper import get_base_model, get_iptv_url, on_popup_menu
from .uicommons import Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey
@@ -235,7 +235,8 @@ class IptvDialog:
self._name_entry.set_text(title)
if link:
- entry.set_text(link)
+ # TODO implement the choice of quality.
+ entry.set_text(link[sorted(link, key=lambda x: int(x.rstrip("p")), reverse=True)[0]])
else:
msg = get_message("Get link error:") + " No link received for id: {}".format(video_id)
self.show_info_message(msg, Gtk.MessageType.ERROR)
@@ -560,8 +561,8 @@ class YtListImportDialog:
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
- ("yt_import_dialog_window", "yt_liststore", "yt_popup_menu",
- "remove_selection_image"))
+ ("yt_import_dialog_window", "yt_liststore", "yt_quality_liststore",
+ "yt_popup_menu", "remove_selection_image"))
builder.connect_signals(handlers)
self._dialog = builder.get_object("yt_import_dialog_window")
@@ -576,6 +577,11 @@ class YtListImportDialog:
self._url_entry = builder.get_object("yt_url_entry")
self._receive_button = builder.get_object("yt_receive_button")
self._import_button = builder.get_object("yt_import_button")
+ self._quality_box = builder.get_object("yt_quality_combobox")
+ self._quality_model = builder.get_object("yt_quality_liststore")
+ self._import_button.bind_property("visible", self._quality_box, "visible")
+ self._import_button.bind_property("sensitive", self._quality_box, "sensitive")
+ self._receive_button.bind_property("sensitive", self._import_button, "sensitive")
# style
self._style_provider = Gtk.CssProvider()
self._style_provider.load_from_path(UI_RESOURCES_PATH + "style.css")
@@ -650,7 +656,9 @@ class YtListImportDialog:
for l in links:
yield self._model.append((l[0], l[1], True, None))
- self._yt_count_label.set_text(str(len(self._model)))
+ size = len(self._model)
+ self._yt_count_label.set_text(str(size))
+ self._import_button.set_visible(size)
yield True
@run_idle
@@ -666,9 +674,12 @@ class YtListImportDialog:
mk = Service(None, None, None, title, *aggr[0:3], BqServiceType.MARKER.name, *aggr, data_id, fav_id, None)
srvs.append(mk)
- for l in links:
- fav_id = get_fav_id(*l, self._profile)
- srv = Service(None, None, IPTV_ICON, l[1], *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
+ act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
+ for link in links:
+ lnk, title = link
+ ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
+ fav_id = get_fav_id(ln, title, self._profile)
+ srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
srvs.append(srv)
self.appender(srvs)
@@ -676,7 +687,6 @@ class YtListImportDialog:
def update_active_elements(self, sensitive):
self._url_entry.set_sensitive(sensitive)
self._receive_button.set_sensitive(sensitive)
- self._import_button.set_sensitive(sensitive)
def show_invisible_elements(self):
self._list_view_scrolled_window.set_visible(True)