mirror of
https://github.com/DYefremov/DemonEditor.git
synced 2026-05-07 17:16:03 +02:00
added basic youtube-dl support
This commit is contained in:
156
app/tools/yt.py
156
app/tools/yt.py
@@ -1,11 +1,15 @@
|
|||||||
""" Module for working with YouTube service """
|
""" Module for working with YouTube service """
|
||||||
import gzip
|
import gzip
|
||||||
import json
|
import json
|
||||||
|
import os
|
||||||
import re
|
import re
|
||||||
import urllib
|
import shutil
|
||||||
|
import sys
|
||||||
from html.parser import HTMLParser
|
from html.parser import HTMLParser
|
||||||
from json import JSONDecodeError
|
from json import JSONDecodeError
|
||||||
from urllib.request import Request
|
from urllib.error import URLError
|
||||||
|
from urllib.parse import unquote
|
||||||
|
from urllib.request import Request, urlopen, urlretrieve
|
||||||
|
|
||||||
from app.commons import log
|
from app.commons import log
|
||||||
|
|
||||||
@@ -23,6 +27,8 @@ Quality = {137: "1080p", 136: "720p", 135: "480p", 134: "360p",
|
|||||||
class YouTube:
|
class YouTube:
|
||||||
""" Helper class for working with YouTube service. """
|
""" Helper class for working with YouTube service. """
|
||||||
|
|
||||||
|
_VIDEO_INFO_LINK = "https://youtube.com/get_video_info?video_id={}&hl=en"
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def is_yt_video_link(url):
|
def is_yt_video_link(url):
|
||||||
return re.match(_YT_VIDEO_PATTERN, url)
|
return re.match(_YT_VIDEO_PATTERN, url)
|
||||||
@@ -47,11 +53,11 @@ class YouTube:
|
|||||||
|
|
||||||
returns tuple from the video links dict and title
|
returns tuple from the video links dict and title
|
||||||
"""
|
"""
|
||||||
req = Request("https://youtube.com/get_video_info?video_id={}&hl=en".format(video_id), headers=_HEADERS)
|
req = Request(YouTube._VIDEO_INFO_LINK.format(video_id), headers=_HEADERS)
|
||||||
|
|
||||||
with urllib.request.urlopen(req, timeout=2) as resp:
|
with urlopen(req, timeout=2) as resp:
|
||||||
data = urllib.request.unquote(gzip.decompress(resp.read()).decode("utf-8")).split("&")
|
data = unquote(gzip.decompress(resp.read()).decode("utf-8")).split("&")
|
||||||
out = {k: v for k, sep, v in (str(d).partition("=") for d in map(urllib.request.unquote, data))}
|
out = {k: v for k, sep, v in (str(d).partition("=") for d in map(unquote, data))}
|
||||||
player_resp = out.get("player_response", None)
|
player_resp = out.get("player_response", None)
|
||||||
|
|
||||||
if player_resp:
|
if player_resp:
|
||||||
@@ -76,7 +82,7 @@ class YouTube:
|
|||||||
if stream_map:
|
if stream_map:
|
||||||
s_map = {k: v for k, sep, v in (str(d).partition("=") for d in stream_map.split("&"))}
|
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)
|
url, title = s_map.get("url", None), out.get("title", None)
|
||||||
url, title = urllib.request.unquote(url) if url else "", title.replace("+", " ") if title else ""
|
url, title = unquote(url) if url else "", title.replace("+", " ") if title else ""
|
||||||
if url and title:
|
if url and title:
|
||||||
return {Quality[0]: url}, title.replace("+", " ")
|
return {Quality[0]: url}, title.replace("+", " ")
|
||||||
|
|
||||||
@@ -145,13 +151,147 @@ class PlayListParser(HTMLParser):
|
|||||||
"""
|
"""
|
||||||
request = Request("https://www.youtube.com/playlist?list={}&hl=en".format(play_list_id), headers=_HEADERS)
|
request = Request("https://www.youtube.com/playlist?list={}&hl=en".format(play_list_id), headers=_HEADERS)
|
||||||
|
|
||||||
with urllib.request.urlopen(request, timeout=2) as resp:
|
with urlopen(request, timeout=2) as resp:
|
||||||
data = gzip.decompress(resp.read()).decode("utf-8")
|
data = gzip.decompress(resp.read()).decode("utf-8")
|
||||||
parser = PlayListParser()
|
parser = PlayListParser()
|
||||||
parser.feed(data)
|
parser.feed(data)
|
||||||
return parser.header, parser.playlist
|
return parser.header, parser.playlist
|
||||||
|
|
||||||
|
|
||||||
|
class YouTubeDL:
|
||||||
|
""" Utility class [experimental] for working with youtube-dl.
|
||||||
|
|
||||||
|
[https://github.com/ytdl-org/youtube-dl]
|
||||||
|
"""
|
||||||
|
|
||||||
|
_DL_INSTANCE = None
|
||||||
|
_DownloadError = None
|
||||||
|
_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
|
||||||
|
|
||||||
|
def __init__(self, settings, callback):
|
||||||
|
self._path = settings.default_data_path + "tools/"
|
||||||
|
self._update = settings.enable_yt_dl_update
|
||||||
|
self._supported = {"22", "18"}
|
||||||
|
self._dl = None
|
||||||
|
self._callback = callback
|
||||||
|
self._download_exception = None
|
||||||
|
self._is_update_process = False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def get_instance(cls, settings, callback=print):
|
||||||
|
if not cls._DL_INSTANCE:
|
||||||
|
cls._DL_INSTANCE = YouTubeDL(settings, callback)
|
||||||
|
return cls._DL_INSTANCE
|
||||||
|
|
||||||
|
def init(self):
|
||||||
|
if not os.path.isfile(self._path + "youtube_dl/version.py"):
|
||||||
|
self.get_latest_release()
|
||||||
|
|
||||||
|
if self._path not in sys.path:
|
||||||
|
sys.path.append(self._path)
|
||||||
|
|
||||||
|
self.init_dl()
|
||||||
|
|
||||||
|
def init_dl(self):
|
||||||
|
try:
|
||||||
|
import youtube_dl
|
||||||
|
except ModuleNotFoundError as e:
|
||||||
|
log("YouTubeDLHelper error: {}".format(str(e)))
|
||||||
|
raise self.YouTubeDLException(e)
|
||||||
|
else:
|
||||||
|
if self._update:
|
||||||
|
if hasattr(youtube_dl.version, "__version__"):
|
||||||
|
l_ver = self.get_last_release_id()
|
||||||
|
cur_ver = youtube_dl.version.__version__
|
||||||
|
if youtube_dl.version.__version__ < l_ver:
|
||||||
|
msg = "youtube-dl has new release! Current: {}. Last: {}.".format(cur_ver, l_ver)
|
||||||
|
log(msg)
|
||||||
|
self._callback(msg, False)
|
||||||
|
self.get_latest_release()
|
||||||
|
|
||||||
|
self._DownloadError = youtube_dl.utils.DownloadError
|
||||||
|
self._dl = youtube_dl.YoutubeDL(self._OPTIONS)
|
||||||
|
log("youtube-dl initialized...")
|
||||||
|
|
||||||
|
def get_last_release_id(self):
|
||||||
|
""" Getting last release id. """
|
||||||
|
url = "https://api.github.com/repos/ytdl-org/youtube-dl/releases/latest"
|
||||||
|
with urlopen(url, timeout=10) as resp:
|
||||||
|
return json.load(resp).get("tag_name", "0")
|
||||||
|
|
||||||
|
def get_latest_release(self):
|
||||||
|
try:
|
||||||
|
self._is_update_process = True
|
||||||
|
log("Getting the last youtube-dl release...")
|
||||||
|
|
||||||
|
with urlopen(YouTubeDL._LATEST_RELEASE_URL, timeout=10) as resp:
|
||||||
|
r = json.load(resp)
|
||||||
|
zip_url = r.get("zipball_url", None)
|
||||||
|
if zip_url:
|
||||||
|
zip_file = self._path + "yt.zip"
|
||||||
|
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||||
|
f_name, headers = urlretrieve(zip_url, filename=zip_file)
|
||||||
|
|
||||||
|
import zipfile
|
||||||
|
|
||||||
|
with zipfile.ZipFile(f_name) as arch:
|
||||||
|
|
||||||
|
if os.path.isdir(self._path):
|
||||||
|
shutil.rmtree(self._path)
|
||||||
|
else:
|
||||||
|
os.makedirs(os.path.dirname(self._path), exist_ok=True)
|
||||||
|
|
||||||
|
for info in arch.infolist():
|
||||||
|
pref, sep, f = info.filename.partition("/youtube_dl/")
|
||||||
|
if sep:
|
||||||
|
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."
|
||||||
|
log(msg)
|
||||||
|
self._callback(msg, False)
|
||||||
|
return True
|
||||||
|
except URLError as e:
|
||||||
|
log("YouTubeDLHelper error: {}".format(e))
|
||||||
|
raise self.YouTubeDLException(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 {}, ""
|
||||||
|
|
||||||
|
try:
|
||||||
|
info = self._dl.extract_info(url, download=False)
|
||||||
|
except URLError as e:
|
||||||
|
log(str(e))
|
||||||
|
raise self.YouTubeDLException(e)
|
||||||
|
except self._DownloadError as e:
|
||||||
|
log(str(e))
|
||||||
|
if not skip_errors:
|
||||||
|
raise self.YouTubeDLException(e)
|
||||||
|
else:
|
||||||
|
fmts = info.get("formats", None)
|
||||||
|
if fmts:
|
||||||
|
return {Quality.get(int(fm["format_id"])): fm.get("url", "") for fm in fmts if
|
||||||
|
fm.get("format_id", "") in self._supported}, info.get("title", "")
|
||||||
|
|
||||||
|
return {}, info.get("title", "")
|
||||||
|
|
||||||
|
|
||||||
def flat(key, d):
|
def flat(key, d):
|
||||||
for k, v in d.items():
|
for k, v in d.items():
|
||||||
if k == key:
|
if k == key:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
The MIT License (MIT)
|
The MIT License (MIT)
|
||||||
|
|
||||||
Copyright (c) 2018-2019 Dmitriy Yefremov
|
Copyright (c) 2018-2020 Dmitriy Yefremov
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
@@ -31,7 +31,7 @@ Author: Dmitriy Yefremov
|
|||||||
<!-- interface-license-type mit -->
|
<!-- interface-license-type mit -->
|
||||||
<!-- interface-name DemonEditor -->
|
<!-- interface-name DemonEditor -->
|
||||||
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
<!-- interface-description Enigma2 channel and satellites list editor for GNU/Linux. -->
|
||||||
<!-- interface-copyright 2018-2019 Dmitriy Yefremov -->
|
<!-- interface-copyright 2018-2020 Dmitriy Yefremov -->
|
||||||
<!-- interface-authors Dmitriy Yefremov -->
|
<!-- interface-authors Dmitriy Yefremov -->
|
||||||
<object class="GtkImage" id="remove_selection_image">
|
<object class="GtkImage" id="remove_selection_image">
|
||||||
<property name="visible">True</property>
|
<property name="visible">True</property>
|
||||||
@@ -77,9 +77,6 @@ Author: Dmitriy Yefremov
|
|||||||
<property name="decorated">False</property>
|
<property name="decorated">False</property>
|
||||||
<property name="gravity">center</property>
|
<property name="gravity">center</property>
|
||||||
<signal name="response" handler="on_response" swapped="no"/>
|
<signal name="response" handler="on_response" swapped="no"/>
|
||||||
<child type="titlebar">
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<child internal-child="vbox">
|
<child internal-child="vbox">
|
||||||
<object class="GtkBox" id="search_unavailable_dialog_box">
|
<object class="GtkBox" id="search_unavailable_dialog_box">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
@@ -251,6 +248,9 @@ Author: Dmitriy Yefremov
|
|||||||
<row>
|
<row>
|
||||||
<col id="0">none-REC2</col>
|
<col id="0">none-REC2</col>
|
||||||
</row>
|
</row>
|
||||||
|
<row>
|
||||||
|
<col id="0">eServiceUri</col>
|
||||||
|
</row>
|
||||||
</data>
|
</data>
|
||||||
</object>
|
</object>
|
||||||
<object class="GtkDialog" id="iptv_dialog">
|
<object class="GtkDialog" id="iptv_dialog">
|
||||||
@@ -293,9 +293,6 @@ Author: Dmitriy Yefremov
|
|||||||
<signal name="clicked" handler="on_save" swapped="no"/>
|
<signal name="clicked" handler="on_save" swapped="no"/>
|
||||||
</object>
|
</object>
|
||||||
</child>
|
</child>
|
||||||
<child type="titlebar">
|
|
||||||
<placeholder/>
|
|
||||||
</child>
|
|
||||||
<child internal-child="vbox">
|
<child internal-child="vbox">
|
||||||
<object class="GtkBox" id="iptv_dialog_box">
|
<object class="GtkBox" id="iptv_dialog_box">
|
||||||
<property name="can_focus">False</property>
|
<property name="can_focus">False</property>
|
||||||
|
|||||||
131
app/ui/iptv.py
131
app/ui/iptv.py
@@ -2,7 +2,7 @@ import concurrent.futures
|
|||||||
import re
|
import re
|
||||||
import urllib
|
import urllib
|
||||||
from urllib.error import HTTPError
|
from urllib.error import HTTPError
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse, unquote, quote
|
||||||
from urllib.request import Request, urlopen
|
from urllib.request import Request, urlopen
|
||||||
|
|
||||||
from gi.repository import GLib
|
from gi.repository import GLib
|
||||||
@@ -11,11 +11,11 @@ from app.commons import run_idle, run_task
|
|||||||
from app.eparser.ecommons import BqServiceType, Service
|
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.eparser.iptv import NEUTRINO_FAV_ID_FORMAT, StreamType, ENIGMA2_FAV_ID_FORMAT, get_fav_id, MARKER_FORMAT
|
||||||
from app.settings import SettingsType
|
from app.settings import SettingsType
|
||||||
from app.tools.yt import YouTube, PlayListParser
|
from app.tools.yt import YouTube, PlayListParser, YouTubeDL
|
||||||
from .dialogs import Action, show_dialog, DialogType, get_dialogs_string, get_message
|
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 .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, \
|
from .uicommons import (Gtk, Gdk, TEXT_DOMAIN, UI_RESOURCES_PATH, IPTV_ICON, Column, IS_GNOME_SESSION, KeyboardKey,
|
||||||
get_yt_icon
|
get_yt_icon)
|
||||||
|
|
||||||
_DIGIT_ENTRY_NAME = "digit-entry"
|
_DIGIT_ENTRY_NAME = "digit-entry"
|
||||||
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
_ENIGMA2_REFERENCE = "{}:0:{}:{:X}:{:X}:{:X}:{:X}:0:0:0"
|
||||||
@@ -38,12 +38,14 @@ def get_stream_type(box):
|
|||||||
return StreamType.NONE_TS.value
|
return StreamType.NONE_TS.value
|
||||||
elif active == 2:
|
elif active == 2:
|
||||||
return StreamType.NONE_REC_1.value
|
return StreamType.NONE_REC_1.value
|
||||||
return StreamType.NONE_REC_2.value
|
elif active == 3:
|
||||||
|
return StreamType.NONE_REC_2.value
|
||||||
|
return StreamType.E_SERVICE_URI.value
|
||||||
|
|
||||||
|
|
||||||
class IptvDialog:
|
class IptvDialog:
|
||||||
|
|
||||||
def __init__(self, transient, view, services, bouquet, profile=SettingsType.ENIGMA_2, action=Action.ADD):
|
def __init__(self, transient, view, services, bouquet, settings, action=Action.ADD):
|
||||||
handlers = {"on_response": self.on_response,
|
handlers = {"on_response": self.on_response,
|
||||||
"on_entry_changed": self.on_entry_changed,
|
"on_entry_changed": self.on_entry_changed,
|
||||||
"on_url_changed": self.on_url_changed,
|
"on_url_changed": self.on_url_changed,
|
||||||
@@ -52,18 +54,20 @@ class IptvDialog:
|
|||||||
"on_yt_quality_changed": self.on_yt_quality_changed,
|
"on_yt_quality_changed": self.on_yt_quality_changed,
|
||||||
"on_info_bar_close": self.on_info_bar_close}
|
"on_info_bar_close": self.on_info_bar_close}
|
||||||
|
|
||||||
|
self._action = action
|
||||||
|
self._s_type = settings.setting_type
|
||||||
|
self._settings = settings
|
||||||
|
self._bouquet = bouquet
|
||||||
|
self._services = services
|
||||||
|
self._yt_links = None
|
||||||
|
self._yt_dl = None
|
||||||
|
|
||||||
builder = Gtk.Builder()
|
builder = Gtk.Builder()
|
||||||
builder.set_translation_domain(TEXT_DOMAIN)
|
builder.set_translation_domain(TEXT_DOMAIN)
|
||||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||||
("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
|
("iptv_dialog", "stream_type_liststore", "yt_quality_liststore"))
|
||||||
builder.connect_signals(handlers)
|
builder.connect_signals(handlers)
|
||||||
|
|
||||||
self._action = action
|
|
||||||
self._profile = profile
|
|
||||||
self._bouquet = bouquet
|
|
||||||
self._services = services
|
|
||||||
self._yt_links = None
|
|
||||||
|
|
||||||
self._dialog = builder.get_object("iptv_dialog")
|
self._dialog = builder.get_object("iptv_dialog")
|
||||||
self._dialog.set_transient_for(transient)
|
self._dialog.set_transient_for(transient)
|
||||||
self._name_entry = builder.get_object("name_entry")
|
self._name_entry = builder.get_object("name_entry")
|
||||||
@@ -91,7 +95,7 @@ class IptvDialog:
|
|||||||
for el in self._digit_elems:
|
for el in self._digit_elems:
|
||||||
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
el.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||||
if profile is SettingsType.NEUTRINO_MP:
|
if self._s_type is SettingsType.NEUTRINO_MP:
|
||||||
builder.get_object("iptv_dialog_ts_data_frame").set_visible(False)
|
builder.get_object("iptv_dialog_ts_data_frame").set_visible(False)
|
||||||
builder.get_object("iptv_type_label").set_visible(False)
|
builder.get_object("iptv_type_label").set_visible(False)
|
||||||
builder.get_object("reference_entry").set_visible(False)
|
builder.get_object("reference_entry").set_visible(False)
|
||||||
@@ -104,8 +108,8 @@ class IptvDialog:
|
|||||||
if self._action is Action.ADD:
|
if self._action is Action.ADD:
|
||||||
self._save_button.set_visible(False)
|
self._save_button.set_visible(False)
|
||||||
self._add_button.set_visible(True)
|
self._add_button.set_visible(True)
|
||||||
if self._profile is SettingsType.ENIGMA_2:
|
if self._s_type is SettingsType.ENIGMA_2:
|
||||||
self._update_reference_entry()
|
self.update_reference_entry()
|
||||||
self._stream_type_combobox.set_active(1)
|
self._stream_type_combobox.set_active(1)
|
||||||
elif self._action is Action.EDIT:
|
elif self._action is Action.EDIT:
|
||||||
self._current_srv = get_base_model(self._model)[self._paths][:]
|
self._current_srv = get_base_model(self._model)[self._paths][:]
|
||||||
@@ -129,13 +133,13 @@ class IptvDialog:
|
|||||||
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
if show_dialog(DialogType.QUESTION, self._dialog) == Gtk.ResponseType.CANCEL:
|
||||||
return
|
return
|
||||||
|
|
||||||
self.save_enigma2_data() if self._profile is SettingsType.ENIGMA_2 else self.save_neutrino_data()
|
self.save_enigma2_data() if self._s_type is SettingsType.ENIGMA_2 else self.save_neutrino_data()
|
||||||
self._dialog.destroy()
|
self._dialog.destroy()
|
||||||
|
|
||||||
def init_data(self, srv):
|
def init_data(self, srv):
|
||||||
name, fav_id = srv[2], srv[7]
|
name, fav_id = srv[2], srv[7]
|
||||||
self._name_entry.set_text(name)
|
self._name_entry.set_text(name)
|
||||||
self.init_enigma2_data(fav_id) if self._profile is SettingsType.ENIGMA_2 else self.init_neutrino_data(fav_id)
|
self.init_enigma2_data(fav_id) if self._s_type is SettingsType.ENIGMA_2 else self.init_neutrino_data(fav_id)
|
||||||
|
|
||||||
def init_enigma2_data(self, fav_id):
|
def init_enigma2_data(self, fav_id):
|
||||||
data, sep, desc = fav_id.partition("#DESCRIPTION")
|
data, sep, desc = fav_id.partition("#DESCRIPTION")
|
||||||
@@ -155,6 +159,8 @@ class IptvDialog:
|
|||||||
self._stream_type_combobox.set_active(2)
|
self._stream_type_combobox.set_active(2)
|
||||||
elif stream_type is StreamType.NONE_REC_2:
|
elif stream_type is StreamType.NONE_REC_2:
|
||||||
self._stream_type_combobox.set_active(3)
|
self._stream_type_combobox.set_active(3)
|
||||||
|
elif stream_type is StreamType.E_SERVICE_URI:
|
||||||
|
self._stream_type_combobox.set_active(4)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
|
self.show_info_message("Unknown stream type {}".format(s_type), Gtk.MessageType.ERROR)
|
||||||
|
|
||||||
@@ -163,16 +169,17 @@ class IptvDialog:
|
|||||||
self._tr_id_entry.set_text(str(int(data[4], 16)))
|
self._tr_id_entry.set_text(str(int(data[4], 16)))
|
||||||
self._net_id_entry.set_text(str(int(data[5], 16)))
|
self._net_id_entry.set_text(str(int(data[5], 16)))
|
||||||
self._namespace_entry.set_text(str(int(data[6], 16)))
|
self._namespace_entry.set_text(str(int(data[6], 16)))
|
||||||
self._url_entry.set_text(urllib.request.unquote(data[10].strip()))
|
self._url_entry.set_text(unquote(data[10].strip()))
|
||||||
self._update_reference_entry()
|
self.update_reference_entry()
|
||||||
|
|
||||||
def init_neutrino_data(self, fav_id):
|
def init_neutrino_data(self, fav_id):
|
||||||
data = fav_id.split("::")
|
data = fav_id.split("::")
|
||||||
self._url_entry.set_text(data[0])
|
self._url_entry.set_text(data[0])
|
||||||
self._description_entry.set_text(data[1])
|
self._description_entry.set_text(data[1])
|
||||||
|
|
||||||
def _update_reference_entry(self):
|
def update_reference_entry(self):
|
||||||
if self._profile is SettingsType.ENIGMA_2:
|
if self._s_type is SettingsType.ENIGMA_2:
|
||||||
|
self.on_url_changed(self._url_entry)
|
||||||
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
|
self._reference_entry.set_text(_ENIGMA2_REFERENCE.format(self.get_type(),
|
||||||
self._srv_type_entry.get_text(),
|
self._srv_type_entry.get_text(),
|
||||||
int(self._sid_entry.get_text()),
|
int(self._sid_entry.get_text()),
|
||||||
@@ -188,12 +195,13 @@ class IptvDialog:
|
|||||||
entry.set_name(_DIGIT_ENTRY_NAME)
|
entry.set_name(_DIGIT_ENTRY_NAME)
|
||||||
else:
|
else:
|
||||||
entry.set_name("GtkEntry")
|
entry.set_name("GtkEntry")
|
||||||
self._update_reference_entry()
|
self.update_reference_entry()
|
||||||
|
|
||||||
def on_url_changed(self, entry):
|
def on_url_changed(self, entry):
|
||||||
url_str = entry.get_text()
|
url_str = entry.get_text()
|
||||||
url = urlparse(url_str)
|
url = urlparse(url_str)
|
||||||
entry.set_name("GtkEntry" if all([url.scheme, url.netloc, url.path]) else _DIGIT_ENTRY_NAME)
|
cond = all([url.scheme, url.netloc, url.path]) or self.get_type() == StreamType.E_SERVICE_URI.value
|
||||||
|
entry.set_name("GtkEntry" if cond else _DIGIT_ENTRY_NAME)
|
||||||
|
|
||||||
yt_id = YouTube.get_yt_id(url_str)
|
yt_id = YouTube.get_yt_id(url_str)
|
||||||
if yt_id:
|
if yt_id:
|
||||||
@@ -211,10 +219,22 @@ class IptvDialog:
|
|||||||
|
|
||||||
def set_yt_url(self, entry, video_id):
|
def set_yt_url(self, entry, video_id):
|
||||||
try:
|
try:
|
||||||
links, title = YouTube.get_yt_link(video_id)
|
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)
|
||||||
except urllib.error.URLError as e:
|
except urllib.error.URLError as e:
|
||||||
self.show_info_message(get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR)
|
self.show_info_message(get_message("Getting link error:") + (str(e)), Gtk.MessageType.ERROR)
|
||||||
return
|
return
|
||||||
|
except YouTubeDL.YouTubeDLException as e:
|
||||||
|
self.show_info_message((str(e)), Gtk.MessageType.ERROR)
|
||||||
|
return
|
||||||
else:
|
else:
|
||||||
if self._action is Action.ADD:
|
if self._action is Action.ADD:
|
||||||
self._name_entry.set_text(title)
|
self._name_entry.set_text(title)
|
||||||
@@ -232,7 +252,9 @@ class IptvDialog:
|
|||||||
yield True
|
yield True
|
||||||
|
|
||||||
def on_stream_type_changed(self, item):
|
def on_stream_type_changed(self, item):
|
||||||
self._update_reference_entry()
|
if self.get_type() == StreamType.E_SERVICE_URI.value:
|
||||||
|
self.show_info_message("DreamOS only!", Gtk.MessageType.WARNING)
|
||||||
|
self.update_reference_entry()
|
||||||
|
|
||||||
def on_yt_quality_changed(self, box):
|
def on_yt_quality_changed(self, box):
|
||||||
model = box.get_model()
|
model = box.get_model()
|
||||||
@@ -248,7 +270,7 @@ class IptvDialog:
|
|||||||
int(self._tr_id_entry.get_text()),
|
int(self._tr_id_entry.get_text()),
|
||||||
int(self._net_id_entry.get_text()),
|
int(self._net_id_entry.get_text()),
|
||||||
int(self._namespace_entry.get_text()),
|
int(self._namespace_entry.get_text()),
|
||||||
urllib.request.quote(self._url_entry.get_text()),
|
quote(self._url_entry.get_text()),
|
||||||
name, name)
|
name, name)
|
||||||
self.update_bouquet_data(name, fav_id)
|
self.update_bouquet_data(name, fav_id)
|
||||||
|
|
||||||
@@ -291,7 +313,7 @@ class IptvDialog:
|
|||||||
|
|
||||||
class SearchUnavailableDialog:
|
class SearchUnavailableDialog:
|
||||||
|
|
||||||
def __init__(self, transient, model, fav_bouquet, iptv_rows, profile):
|
def __init__(self, transient, model, fav_bouquet, iptv_rows, s_type):
|
||||||
handlers = {"on_response": self.on_response}
|
handlers = {"on_response": self.on_response}
|
||||||
|
|
||||||
builder = Gtk.Builder()
|
builder = Gtk.Builder()
|
||||||
@@ -305,7 +327,7 @@ class SearchUnavailableDialog:
|
|||||||
self._counter_label = builder.get_object("streams_rows_counter_label")
|
self._counter_label = builder.get_object("streams_rows_counter_label")
|
||||||
self._level_bar = builder.get_object("unavailable_streams_level_bar")
|
self._level_bar = builder.get_object("unavailable_streams_level_bar")
|
||||||
self._bouquet = fav_bouquet
|
self._bouquet = fav_bouquet
|
||||||
self._profile = profile
|
self._s_type = s_type
|
||||||
self._iptv_rows = iptv_rows
|
self._iptv_rows = iptv_rows
|
||||||
self._counter = -1
|
self._counter = -1
|
||||||
self._max_rows = len(self._iptv_rows)
|
self._max_rows = len(self._iptv_rows)
|
||||||
@@ -333,7 +355,7 @@ class SearchUnavailableDialog:
|
|||||||
if not self._download_task:
|
if not self._download_task:
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
req = Request(get_iptv_url(row, self._profile))
|
req = Request(get_iptv_url(row, self._s_type))
|
||||||
self.update_bar()
|
self.update_bar()
|
||||||
urlopen(req, timeout=2)
|
urlopen(req, timeout=2)
|
||||||
except HTTPError as e:
|
except HTTPError as e:
|
||||||
@@ -375,7 +397,7 @@ class SearchUnavailableDialog:
|
|||||||
|
|
||||||
class IptvListConfigurationDialog:
|
class IptvListConfigurationDialog:
|
||||||
|
|
||||||
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, profile):
|
def __init__(self, transient, services, iptv_rows, bouquet, fav_model, s_type):
|
||||||
handlers = {"on_apply": self.on_apply,
|
handlers = {"on_apply": self.on_apply,
|
||||||
"on_response": self.on_response,
|
"on_response": self.on_response,
|
||||||
"on_stream_type_default_togged": self.on_stream_type_default_togged,
|
"on_stream_type_default_togged": self.on_stream_type_default_togged,
|
||||||
@@ -389,18 +411,18 @@ class IptvListConfigurationDialog:
|
|||||||
"on_entry_changed": self.on_entry_changed,
|
"on_entry_changed": self.on_entry_changed,
|
||||||
"on_info_bar_close": self.on_info_bar_close}
|
"on_info_bar_close": self.on_info_bar_close}
|
||||||
|
|
||||||
|
self._rows = iptv_rows
|
||||||
|
self._services = services
|
||||||
|
self._bouquet = bouquet
|
||||||
|
self._fav_model = fav_model
|
||||||
|
self._s_type = s_type
|
||||||
|
|
||||||
builder = Gtk.Builder()
|
builder = Gtk.Builder()
|
||||||
builder.set_translation_domain(TEXT_DOMAIN)
|
builder.set_translation_domain(TEXT_DOMAIN)
|
||||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||||
("iptv_list_configuration_dialog", "stream_type_liststore"))
|
("iptv_list_configuration_dialog", "stream_type_liststore"))
|
||||||
builder.connect_signals(handlers)
|
builder.connect_signals(handlers)
|
||||||
|
|
||||||
self._rows = iptv_rows
|
|
||||||
self._services = services
|
|
||||||
self._bouquet = bouquet
|
|
||||||
self._fav_model = fav_model
|
|
||||||
self._profile = profile
|
|
||||||
|
|
||||||
self._dialog = builder.get_object("iptv_list_configuration_dialog")
|
self._dialog = builder.get_object("iptv_list_configuration_dialog")
|
||||||
self._dialog.set_transient_for(transient)
|
self._dialog.set_transient_for(transient)
|
||||||
self._info_bar = builder.get_object("list_configuration_info_bar")
|
self._info_bar = builder.get_object("list_configuration_info_bar")
|
||||||
@@ -487,7 +509,7 @@ class IptvListConfigurationDialog:
|
|||||||
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
show_dialog(DialogType.ERROR, self._dialog, "Error. Verify the data!")
|
||||||
return
|
return
|
||||||
|
|
||||||
if self._profile is SettingsType.ENIGMA_2:
|
if self._s_type is SettingsType.ENIGMA_2:
|
||||||
reset = self._reset_to_default_switch.get_active()
|
reset = self._reset_to_default_switch.get_active()
|
||||||
type_default = self._type_check_button.get_active()
|
type_default = self._type_check_button.get_active()
|
||||||
tid_default = self._tid_check_button.get_active()
|
tid_default = self._tid_check_button.get_active()
|
||||||
@@ -541,7 +563,7 @@ class IptvListConfigurationDialog:
|
|||||||
|
|
||||||
|
|
||||||
class YtListImportDialog:
|
class YtListImportDialog:
|
||||||
def __init__(self, transient, profile, appender):
|
def __init__(self, transient, settings, appender):
|
||||||
handlers = {"on_import": self.on_import,
|
handlers = {"on_import": self.on_import,
|
||||||
"on_receive": self.on_receive,
|
"on_receive": self.on_receive,
|
||||||
"on_yt_url_entry_changed": self.on_url_entry_changed,
|
"on_yt_url_entry_changed": self.on_url_entry_changed,
|
||||||
@@ -553,6 +575,14 @@ class YtListImportDialog:
|
|||||||
"on_key_press": self.on_key_press,
|
"on_key_press": self.on_key_press,
|
||||||
"on_close": self.on_close}
|
"on_close": self.on_close}
|
||||||
|
|
||||||
|
self.appender = appender
|
||||||
|
self._s_type = settings.setting_type
|
||||||
|
self._download_task = False
|
||||||
|
self._yt_list_id = None
|
||||||
|
self._yt_list_title = None
|
||||||
|
self._settings = settings
|
||||||
|
self._yt_dl = None
|
||||||
|
|
||||||
builder = Gtk.Builder()
|
builder = Gtk.Builder()
|
||||||
builder.set_translation_domain(TEXT_DOMAIN)
|
builder.set_translation_domain(TEXT_DOMAIN)
|
||||||
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
builder.add_objects_from_string(get_dialogs_string(_UI_PATH).format(use_header=IS_GNOME_SESSION),
|
||||||
@@ -584,12 +614,6 @@ class YtListImportDialog:
|
|||||||
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
self._url_entry.get_style_context().add_provider_for_screen(Gdk.Screen.get_default(), self._style_provider,
|
||||||
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
Gtk.STYLE_PROVIDER_PRIORITY_USER)
|
||||||
|
|
||||||
self.appender = appender
|
|
||||||
self._profile = profile
|
|
||||||
self._download_task = False
|
|
||||||
self._yt_list_id = None
|
|
||||||
self._yt_list_title = None
|
|
||||||
|
|
||||||
def show(self):
|
def show(self):
|
||||||
self._dialog.show()
|
self._dialog.show()
|
||||||
|
|
||||||
@@ -603,7 +627,14 @@ class YtListImportDialog:
|
|||||||
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
with concurrent.futures.ThreadPoolExecutor(max_workers=4) as executor:
|
||||||
done_links = {}
|
done_links = {}
|
||||||
rows = list(filter(lambda r: r[2], self._model))
|
rows = list(filter(lambda r: r[2], self._model))
|
||||||
futures = {executor.submit(YouTube.get_yt_link, r[1]): r for r in rows}
|
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}
|
||||||
size = len(futures)
|
size = len(futures)
|
||||||
counter = 0
|
counter = 0
|
||||||
|
|
||||||
@@ -615,6 +646,8 @@ class YtListImportDialog:
|
|||||||
done_links[futures[future]] = future.result()
|
done_links[futures[future]] = future.result()
|
||||||
counter += 1
|
counter += 1
|
||||||
self.update_progress_bar(counter / size)
|
self.update_progress_bar(counter / size)
|
||||||
|
except YouTubeDL.YouTubeDLException as e:
|
||||||
|
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
self.show_info_message(str(e), Gtk.MessageType.ERROR)
|
||||||
else:
|
else:
|
||||||
@@ -648,8 +681,8 @@ class YtListImportDialog:
|
|||||||
self.update_active_elements(True)
|
self.update_active_elements(True)
|
||||||
|
|
||||||
def update_links(self, links):
|
def update_links(self, links):
|
||||||
for l in links:
|
for link in links:
|
||||||
yield self._model.append((l[0], l[1], True, None))
|
yield self._model.append((link[0], link[1], True, None))
|
||||||
|
|
||||||
size = len(self._model)
|
size = len(self._model)
|
||||||
self._yt_count_label.set_text(str(size))
|
self._yt_count_label.set_text(str(size))
|
||||||
@@ -669,11 +702,11 @@ class YtListImportDialog:
|
|||||||
|
|
||||||
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
|
act = self._quality_model.get_value(self._quality_box.get_active_iter(), 0)
|
||||||
for link in links:
|
for link in links:
|
||||||
lnk, title = link
|
lnk, title = link or (None, None)
|
||||||
if not lnk:
|
if not lnk:
|
||||||
continue
|
continue
|
||||||
ln = lnk.get(act) if act in lnk else lnk[sorted(lnk, key=lambda x: int(x.rstrip("p")), reverse=True)[0]]
|
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)
|
fav_id = get_fav_id(ln, title, self._s_type)
|
||||||
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
|
srv = Service(None, None, IPTV_ICON, title, *aggr[0:3], BqServiceType.IPTV.name, *aggr, None, fav_id, None)
|
||||||
srvs.append(srv)
|
srvs.append(srv)
|
||||||
self.appender(srvs)
|
self.appender(srvs)
|
||||||
|
|||||||
@@ -1739,7 +1739,7 @@ class Application(Gtk.Application):
|
|||||||
self._fav_view,
|
self._fav_view,
|
||||||
self._services,
|
self._services,
|
||||||
self._bouquets.get(self._bq_selected, None),
|
self._bouquets.get(self._bq_selected, None),
|
||||||
self._s_type,
|
self._settings,
|
||||||
Action.ADD).show()
|
Action.ADD).show()
|
||||||
if response != Gtk.ResponseType.CANCEL:
|
if response != Gtk.ResponseType.CANCEL:
|
||||||
self.update_fav_num_column(self._fav_model)
|
self.update_fav_num_column(self._fav_model)
|
||||||
@@ -1802,7 +1802,7 @@ class Application(Gtk.Application):
|
|||||||
if not self._bq_selected:
|
if not self._bq_selected:
|
||||||
return
|
return
|
||||||
|
|
||||||
YtListImportDialog(self._main_window, self._s_type, self.append_imported_services).show()
|
YtListImportDialog(self._main_window, self._settings, self.append_imported_services).show()
|
||||||
|
|
||||||
def on_import_m3u(self, action, value=None):
|
def on_import_m3u(self, action, value=None):
|
||||||
""" Imports iptv from m3u files. """
|
""" Imports iptv from m3u files. """
|
||||||
@@ -1910,6 +1910,8 @@ class Application(Gtk.Application):
|
|||||||
url = get_iptv_url(row, self._s_type)
|
url = get_iptv_url(row, self._s_type)
|
||||||
self.update_player_buttons()
|
self.update_player_buttons()
|
||||||
if not url:
|
if not url:
|
||||||
|
self.show_error_dialog("No reference is present!")
|
||||||
|
self.set_playback_elms_active()
|
||||||
return
|
return
|
||||||
self.play(url)
|
self.play(url)
|
||||||
|
|
||||||
@@ -2460,7 +2462,7 @@ class Application(Gtk.Application):
|
|||||||
self._fav_view,
|
self._fav_view,
|
||||||
self._services,
|
self._services,
|
||||||
self._bouquets.get(self._bq_selected, None),
|
self._bouquets.get(self._bq_selected, None),
|
||||||
self._s_type,
|
self._settings,
|
||||||
Action.EDIT).show()
|
Action.EDIT).show()
|
||||||
self.on_locate_in_services(view)
|
self.on_locate_in_services(view)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user