small yt refactoring

This commit is contained in:
DYefremov
2020-06-13 20:57:37 +03:00
parent 65c24a324a
commit 65dfd6c1c4
4 changed files with 71 additions and 45 deletions

View File

@@ -14,7 +14,7 @@ from urllib.request import Request, urlopen, urlretrieve
from app.commons import log
_YT_PATTERN = re.compile(r"https://www.youtube.com/.+(?:v=)([\w-]{11}).*")
_YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{23,})?.*")
_YT_LIST_PATTERN = re.compile(r"https://www.youtube.com/.+?(?:list=)([\w-]{18,})?.*")
_YT_VIDEO_PATTERN = re.compile(r"https://r\d+---sn-[\w]{10}-[\w]{3,5}.googlevideo.com/videoplayback?.*")
_HEADERS = {"User-Agent": "Mozilla/5.0 (X11; Linux i586; rv:31.0) Gecko/20100101 Firefox/69.0",
"DNT": "1",
@@ -24,11 +24,32 @@ Quality = {137: "1080p", 136: "720p", 135: "480p", 134: "360p",
133: "240p", 160: "144p", 0: "0p", 18: "360p", 22: "720p"}
class YouTubeException(Exception):
pass
class YouTube:
""" Helper class for working with YouTube service. """
_YT_INSTANCE = None
_VIDEO_INFO_LINK = "https://youtube.com/get_video_info?video_id={}&hl=en"
VIDEO_LINK = "https://www.youtube.com/watch?v={}"
def __init__(self, settings, callback):
self._settings = settings
self._yt_dl = None
self._callback = callback
if self._settings.enable_yt_dl:
self._yt_dl = YouTubeDL.get_instance(self._settings, callback=self._callback)
@classmethod
def get_instance(cls, settings, callback=log):
if not cls._YT_INSTANCE:
cls._YT_INSTANCE = YouTube(settings, callback)
return cls._YT_INSTANCE
@staticmethod
def is_yt_video_link(url):
return re.match(_YT_VIDEO_PATTERN, url)
@@ -47,12 +68,24 @@ class YouTube:
if yt:
return yt.group(1)
@staticmethod
def get_yt_link(video_id):
""" Getting link to YouTube video by id.
def get_yt_link(self, video_id, url=None, skip_errors=False):
""" Getting link to YouTube video by id or URL.
returns tuple from the video links dict and title
Returns tuple from the video links dict and title.
"""
if self._settings.enable_yt_dl and url:
if not self._yt_dl:
self._yt_dl = YouTubeDL.get_instance(self._settings, self._callback)
return self._yt_dl.get_yt_link(url, skip_errors)
return self.get_yt_link_by_id(video_id)
@staticmethod
def get_yt_link_by_id(video_id):
""" Getting link to YouTube video by id.
Returns tuple from the video links dict and title.
"""
req = Request(YouTube._VIDEO_INFO_LINK.format(video_id), headers=_HEADERS)
with urlopen(req, timeout=2) as resp:
@@ -169,12 +202,8 @@ class YouTubeDL:
_LATEST_RELEASE_URL = "https://api.github.com/repos/ytdl-org/youtube-dl/releases/latest"
_OPTIONS = {"noplaylist": True, # Single video instead of a playlist [ignoring playlist in URL].
"quiet": True, # Do not print messages to stdout.
"simulate": True} # Do not download the video files.
VIDEO_LINK = "https://www.youtube.com/watch?v={}"
class YouTubeDLException(Exception):
pass
"simulate": True, # Do not download the video files.
"cookiefile": "cookies.txt"} # File name where cookies should be read from and dumped to.
def __init__(self, settings, callback):
self._path = settings.default_data_path + "tools/"
@@ -185,6 +214,8 @@ class YouTubeDL:
self._download_exception = None
self._is_update_process = False
self.init()
@classmethod
def get_instance(cls, settings, callback=print):
if not cls._DL_INSTANCE:
@@ -205,7 +236,7 @@ class YouTubeDL:
import youtube_dl
except ModuleNotFoundError as e:
log("YouTubeDLHelper error: {}".format(str(e)))
raise self.YouTubeDLException(e)
raise YouTubeException(e)
else:
if self._update:
if hasattr(youtube_dl.version, "__version__"):
@@ -221,7 +252,8 @@ class YouTubeDL:
self._dl = youtube_dl.YoutubeDL(self._OPTIONS)
log("youtube-dl initialized...")
def get_last_release_id(self):
@staticmethod
def get_last_release_id():
""" Getting last release id. """
url = "https://api.github.com/repos/ytdl-org/youtube-dl/releases/latest"
with urlopen(url, timeout=10) as resp:
@@ -255,21 +287,18 @@ class YouTubeDL:
arch.extract(info.filename)
shutil.move(info.filename, "{}{}{}".format(self._path, sep, f))
shutil.rmtree(pref)
msg = "Getting the last youtube-dl release is done! Please restart."
msg = "Getting the last youtube-dl release is done!"
log(msg)
self._callback(msg, False)
return True
except URLError as e:
log("YouTubeDLHelper error: {}".format(e))
raise self.YouTubeDLException(e)
raise YouTubeException(e)
finally:
self._is_update_process = False
def get_yt_link(self, url, skip_errors=False):
""" Returns tuple from the video links [dict] and title. """
if not self._dl:
self.init()
if self._is_update_process:
self._callback("Update process. Please wait.", False)
return {}, ""
@@ -278,11 +307,11 @@ class YouTubeDL:
info = self._dl.extract_info(url, download=False)
except URLError as e:
log(str(e))
raise self.YouTubeDLException(e)
raise YouTubeException(e)
except self._DownloadError as e:
log(str(e))
if not skip_errors:
raise self.YouTubeDLException(e)
raise YouTubeException(e)
else:
fmts = info.get("formats", None)
if fmts:

View File

@@ -11,7 +11,7 @@ from app.commons import run_idle, run_task
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.settings import SettingsType
from app.tools.yt import YouTube, PlayListParser, YouTubeDL
from app.tools.yt import PlayListParser, YouTubeException, YouTube
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,
@@ -219,20 +219,19 @@ class IptvDialog:
def set_yt_url(self, entry, video_id):
try:
if self._settings.enable_yt_dl:
if not self._yt_dl:
def callback(message, error=True):
msg_type = Gtk.MessageType.ERROR if error else Gtk.MessageType.INFO
self.show_info_message(message, msg_type)
self._yt_dl = YouTubeDL.get_instance(self._settings, callback=callback)
yield True
links, title = self._yt_dl.get_yt_link(entry.get_text())
else:
links, title = YouTube.get_yt_link(video_id)
if not self._yt_dl:
def callback(message, error=True):
msg_type = Gtk.MessageType.ERROR if error else Gtk.MessageType.INFO
self.show_info_message(message, msg_type)
self._yt_dl = YouTube.get_instance(self._settings, callback=callback)
yield True
links, title = self._yt_dl.get_yt_link(video_id, entry.get_text())
yield True
except urllib.error.URLError as e:
self.show_info_message(get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR)
return
except YouTubeDL.YouTubeDLException as e:
except YouTubeException as e:
self.show_info_message((str(e)), Gtk.MessageType.ERROR)
return
else:
@@ -581,7 +580,7 @@ class YtListImportDialog:
self._yt_list_id = None
self._yt_list_title = None
self._settings = settings
self._yt_dl = None
self._yt = None
builder = Gtk.Builder()
builder.set_translation_domain(TEXT_DOMAIN)
@@ -626,14 +625,11 @@ class YtListImportDialog:
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
done_links = {}
rows = list(filter(lambda r: r[2], self._model))
if self._settings.enable_yt_dl:
if not self._yt_dl:
self._yt_dl = YouTubeDL.get_instance(self._settings)
futures = {executor.submit(self._yt_dl.get_yt_link,
YouTubeDL.VIDEO_LINK.format(r[1]),
True): r for r in rows}
else:
futures = {executor.submit(YouTube.get_yt_link, r[1]): r for r in rows}
if not self._yt:
self._yt = YouTube.get_instance(self._settings)
futures = {executor.submit(self._yt.get_yt_link, r[1], YouTube.VIDEO_LINK.format(r[1]),
True): r for r in rows}
size = len(futures)
counter = 0
@@ -645,7 +641,7 @@ class YtListImportDialog:
done_links[futures[future]] = future.result()
counter += 1
self.update_progress_bar(counter / size)
except YouTubeDL.YouTubeDLException as e:
except YouTubeException as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)
except Exception as e:
self.show_info_message(str(e), Gtk.MessageType.ERROR)

View File

@@ -2084,7 +2084,7 @@ class Application(Gtk.Application):
@run_idle
def init_send_to(self, enable):
if enable and not self._links_transmitter:
self._links_transmitter = LinksTransmitter(self._http_api, self._main_window)
self._links_transmitter = LinksTransmitter(self._http_api, self._main_window, self._settings)
elif self._links_transmitter:
self._links_transmitter.show(enable)

View File

@@ -18,7 +18,7 @@ class LinksTransmitter:
"""
__STREAM_PREFIX = "4097:0:1:0:0:0:0:0:0:0:"
def __init__(self, http_api, app_window):
def __init__(self, http_api, app_window, settings):
handlers = {"on_popup_menu": self.on_popup_menu,
"on_status_icon_activate": self.on_status_icon_activate,
"on_url_changed": self.on_url_changed,
@@ -45,6 +45,7 @@ class LinksTransmitter:
self._restore_menu_item = builder.get_object("restore_menu_item")
self._status_active = None
self._status_passive = None
self._yt = YouTube.get_instance(settings)
try:
gi.require_version("AppIndicator3", "0.1")
@@ -113,7 +114,7 @@ class LinksTransmitter:
if yt_id:
self._url_entry.set_icon_from_pixbuf(Gtk.EntryIconPosition.SECONDARY, get_yt_icon("youtube", 32))
links, title = YouTube.get_yt_link(yt_id)
links, title = self._yt.get_yt_link(yt_id, url)
yield True
if links:
url = links[sorted(links, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]