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 + + + + + + + + Auto + + + 1080p + + + 720p + + + 480p + + + 360p + + + 240p + + + 144p + + + 480 False @@ -1270,8 +1299,7 @@ Author: Dmitriy Yefremov - True - False + False True True Import @@ -1290,6 +1318,25 @@ Author: Dmitriy Yefremov 1 + + + False + Desired video quality + yt_quality_liststore + 0 + 0 + + + + 0 + + + + + end + 2 + + 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)