Skip to content

Commit

Permalink
feat: add setting to control URL resolver priority
Browse files Browse the repository at this point in the history
  • Loading branch information
vzhd1701 committed Jul 7, 2022
1 parent 71c4fdc commit fc72744
Show file tree
Hide file tree
Showing 11 changed files with 791 additions and 36 deletions.
27 changes: 26 additions & 1 deletion gridplayer/dialogs/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
SUPPORTED_LANGUAGES,
GridMode,
SeekSyncMode,
URLResolver,
VideoAspect,
VideoDriver,
VideoRepeat,
Expand All @@ -22,6 +23,7 @@
from gridplayer.utils.app_dir import get_app_data_dir
from gridplayer.utils.qt import qt_connect, translate
from gridplayer.widgets.language_list import LanguageList
from gridplayer.widgets.resolver_patterns_list import ResolverPatternsList

VIDEO_DRIVERS_MULTIPROCESS = (
VideoDriver.VLC_SW,
Expand Down Expand Up @@ -96,6 +98,9 @@ def __init__(self, parent):
"logging/log_limit_size": self.logLimitSize,
"logging/log_limit_backups": self.logLimitBackups,
"internal/opaque_hw_overlay": self.miscOpaqueHWOverlay,
"streaming/hls_via_streamlink": self.streamingHLSVIAStreamlink,
"streaming/resolver_priority": self.streamingResolverPriority,
"streaming/resolver_priority_patterns": self.streamingResolverPriorityPatterns, # noqa: E501
}

self.ui_customize()
Expand Down Expand Up @@ -138,7 +143,7 @@ def ui_customize(self): # noqa: WPS213
self.section_misc.hide()
self.miscOpaqueHWOverlay.hide()

def ui_fill(self):
def ui_fill(self): # noqa: WPS213
self.fill_playerVideoDriver()
self.fill_gridMode()
self.fill_videoAspect()
Expand All @@ -148,6 +153,7 @@ def ui_fill(self):
self.fill_language()
self.fill_streamQuality()
self.fill_playlistSeekSyncMode()
self.fill_streamingResolverPriority()

def ui_set_limits(self):
self.playerVideoDriverPlayers.setRange(1, MAX_VLC_PROCESSES)
Expand All @@ -165,6 +171,7 @@ def ui_customize_dynamic(self):
self.timeoutOverlay.setEnabled(self.timeoutOverlayFlag.isChecked())
self.logLimitSize.setEnabled(self.logLimit.isChecked())
self.logLimitBackups.setEnabled(self.logLimit.isChecked())
self.streamingWildcardHelp.setVisible(False)

self.switch_page(None)

Expand All @@ -178,6 +185,12 @@ def ui_connect(self):
(self.section_index.itemSelectionChanged, self.keep_index_selection),
(self.logLimit.stateChanged, self.logLimitSize.setEnabled),
(self.logLimit.stateChanged, self.logLimitBackups.setEnabled),
(self.streamingWildcardHelpButton.clicked, self.toggle_wildcard_help),
)

def toggle_wildcard_help(self):
self.streamingWildcardHelp.setVisible(
not self.streamingWildcardHelp.isVisible()
)

def keep_index_selection(self):
Expand All @@ -190,6 +203,7 @@ def switch_page(self, page_name):
translate("SettingsDialog", "Language"): self.page_general_language,
translate("SettingsDialog", "Playlist"): self.page_defaults_playlist,
translate("SettingsDialog", "Video"): self.page_defaults_video,
translate("SettingsDialog", "Streaming"): self.page_misc_streaming,
translate("SettingsDialog", "Logging"): self.page_misc_logging,
translate("SettingsDialog", "Advanced"): self.page_misc_advanced,
}
Expand Down Expand Up @@ -351,6 +365,15 @@ def fill_playlistSeekSyncMode(self):

_fill_combo_box(self.playlistSeekSyncMode, seek_modes)

def fill_streamingResolverPriority(self):
resolvers = {
URLResolver.STREAMLINK: "Streamlink",
URLResolver.YT_DLP: "yt-dlp",
URLResolver.DIRECT: self.tr("Direct"),
}

_fill_combo_box(self.streamingResolverPriority, resolvers)

def driver_selected(self, idx):
driver_id = self.playerVideoDriver.itemData(idx)

Expand All @@ -365,6 +388,7 @@ def load_settings(self):
QSpinBox: lambda e, v: e.setValue(v),
QComboBox: _set_combo_box,
LanguageList: lambda e, v: e.setValue(v),
ResolverPatternsList: lambda e, v: e.setDataRows(v),
}

for setting, element in self.settings_map.items():
Expand All @@ -383,6 +407,7 @@ def save_settings(self):
QSpinBox: "value",
QComboBox: "currentData",
LanguageList: "value",
ResolverPatternsList: "rows_data",
}

for setting, element in self.settings_map.items():
Expand Down
121 changes: 115 additions & 6 deletions gridplayer/dialogs/settings_dialog_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,8 @@ def setupUi(self, SettingsDialog):
self.section_index.addItem(item)
item = QtWidgets.QListWidgetItem()
self.section_index.addItem(item)
item = QtWidgets.QListWidgetItem()
self.section_index.addItem(item)
self.lay_main_2.addWidget(self.section_index)
self.section_page = QtWidgets.QStackedWidget(SettingsDialog)
self.section_page.setMinimumSize(QtCore.QSize(500, 0))
Expand Down Expand Up @@ -89,7 +91,7 @@ def setupUi(self, SettingsDialog):
self.timeoutOverlayFlag = QtWidgets.QCheckBox(self.page_general_player)
self.timeoutOverlayFlag.setObjectName("timeoutOverlayFlag")
self.formLayout_3.setWidget(
0, QtWidgets.QFormLayout.LabelRole, self.timeoutOverlayFlag
1, QtWidgets.QFormLayout.LabelRole, self.timeoutOverlayFlag
)
self.horizontalLayout_2 = QtWidgets.QHBoxLayout()
self.horizontalLayout_2.setObjectName("horizontalLayout_2")
Expand All @@ -100,12 +102,12 @@ def setupUi(self, SettingsDialog):
self.label_3.setObjectName("label_3")
self.horizontalLayout_2.addWidget(self.label_3)
self.formLayout_3.setLayout(
0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2
1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_2
)
self.timeoutMouseHideFlag = QtWidgets.QCheckBox(self.page_general_player)
self.timeoutMouseHideFlag.setObjectName("timeoutMouseHideFlag")
self.formLayout_3.setWidget(
1, QtWidgets.QFormLayout.LabelRole, self.timeoutMouseHideFlag
2, QtWidgets.QFormLayout.LabelRole, self.timeoutMouseHideFlag
)
self.horizontalLayout = QtWidgets.QHBoxLayout()
self.horizontalLayout.setObjectName("horizontalLayout")
Expand All @@ -116,7 +118,23 @@ def setupUi(self, SettingsDialog):
self.label_2.setObjectName("label_2")
self.horizontalLayout.addWidget(self.label_2)
self.formLayout_3.setLayout(
1, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout
2, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout
)
self.timeoutVideoInitLabel = QtWidgets.QLabel(self.page_general_player)
self.timeoutVideoInitLabel.setObjectName("timeoutVideoInitLabel")
self.formLayout_3.setWidget(
0, QtWidgets.QFormLayout.LabelRole, self.timeoutVideoInitLabel
)
self.horizontalLayout_4 = QtWidgets.QHBoxLayout()
self.horizontalLayout_4.setObjectName("horizontalLayout_4")
self.timeoutVideoInit = QtWidgets.QSpinBox(self.page_general_player)
self.timeoutVideoInit.setObjectName("timeoutVideoInit")
self.horizontalLayout_4.addWidget(self.timeoutVideoInit)
self.label_7 = QtWidgets.QLabel(self.page_general_player)
self.label_7.setObjectName("label_7")
self.horizontalLayout_4.addWidget(self.label_7)
self.formLayout_3.setLayout(
0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_4
)
self.lay_section_player.addLayout(self.formLayout_3)
spacerItem = QtWidgets.QSpacerItem(
Expand All @@ -140,6 +158,71 @@ def setupUi(self, SettingsDialog):
self.label_4.setObjectName("label_4")
self.lay_page_general_language.addWidget(self.label_4)
self.section_page.addWidget(self.page_general_language)
self.page_misc_streaming = QtWidgets.QWidget()
self.page_misc_streaming.setObjectName("page_misc_streaming")
self.lay_page_general_streams = QtWidgets.QVBoxLayout(self.page_misc_streaming)
self.lay_page_general_streams.setContentsMargins(0, 0, 0, 0)
self.lay_page_general_streams.setObjectName("lay_page_general_streams")
self.streamingHLSVIAStreamlink = QtWidgets.QCheckBox(self.page_misc_streaming)
self.streamingHLSVIAStreamlink.setObjectName("streamingHLSVIAStreamlink")
self.lay_page_general_streams.addWidget(self.streamingHLSVIAStreamlink)
self.formLayout_7 = QtWidgets.QFormLayout()
self.formLayout_7.setFieldGrowthPolicy(
QtWidgets.QFormLayout.FieldsStayAtSizeHint
)
self.formLayout_7.setObjectName("formLayout_7")
self.label_8 = QtWidgets.QLabel(self.page_misc_streaming)
self.label_8.setObjectName("label_8")
self.formLayout_7.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_8)
self.streamingResolverPriority = QtWidgets.QComboBox(self.page_misc_streaming)
self.streamingResolverPriority.setObjectName("streamingResolverPriority")
self.formLayout_7.setWidget(
0, QtWidgets.QFormLayout.FieldRole, self.streamingResolverPriority
)
self.lay_page_general_streams.addLayout(self.formLayout_7)
self.label_10 = QtWidgets.QLabel(self.page_misc_streaming)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.label_10.setFont(font)
self.label_10.setObjectName("label_10")
self.lay_page_general_streams.addWidget(self.label_10)
self.verticalLayout = QtWidgets.QVBoxLayout()
self.verticalLayout.setObjectName("verticalLayout")
self.streamingResolverPriorityPatterns = ResolverPatternsList(
self.page_misc_streaming
)
self.streamingResolverPriorityPatterns.setObjectName(
"streamingResolverPriorityPatterns"
)
self.verticalLayout.addWidget(self.streamingResolverPriorityPatterns)
self.formLayout_8 = QtWidgets.QFormLayout()
self.formLayout_8.setFieldGrowthPolicy(
QtWidgets.QFormLayout.FieldsStayAtSizeHint
)
self.formLayout_8.setObjectName("formLayout_8")
self.label_11 = QtWidgets.QLabel(self.page_misc_streaming)
font = QtGui.QFont()
font.setBold(True)
font.setWeight(75)
self.label_11.setFont(font)
self.label_11.setObjectName("label_11")
self.formLayout_8.setWidget(0, QtWidgets.QFormLayout.LabelRole, self.label_11)
self.streamingWildcardHelpButton = QtWidgets.QPushButton(
self.page_misc_streaming
)
self.streamingWildcardHelpButton.setMaximumSize(QtCore.QSize(24, 24))
self.streamingWildcardHelpButton.setObjectName("streamingWildcardHelpButton")
self.formLayout_8.setWidget(
0, QtWidgets.QFormLayout.FieldRole, self.streamingWildcardHelpButton
)
self.verticalLayout.addLayout(self.formLayout_8)
self.streamingWildcardHelp = QtWidgets.QLabel(self.page_misc_streaming)
self.streamingWildcardHelp.setObjectName("streamingWildcardHelp")
self.verticalLayout.addWidget(self.streamingWildcardHelp)
self.verticalLayout.setStretch(0, 1)
self.lay_page_general_streams.addLayout(self.verticalLayout)
self.section_page.addWidget(self.page_misc_streaming)
self.page_defaults_playlist = QtWidgets.QWidget()
self.page_defaults_playlist.setObjectName("page_defaults_playlist")
self.lay_page_defaults_playlist = QtWidgets.QVBoxLayout(
Expand Down Expand Up @@ -414,7 +497,7 @@ def setupUi(self, SettingsDialog):
self.lay_main.setStretch(0, 1)

self.retranslateUi(SettingsDialog)
self.section_page.setCurrentIndex(5)
self.section_page.setCurrentIndex(2)
self.buttonBox.accepted.connect(SettingsDialog.accept)
self.buttonBox.rejected.connect(SettingsDialog.reject)
QtCore.QMetaObject.connectSlotsByName(SettingsDialog)
Expand All @@ -439,8 +522,10 @@ def retranslateUi(self, SettingsDialog):
item = self.section_index.item(6)
item.setText(_translate("SettingsDialog", "Miscellaneous"))
item = self.section_index.item(7)
item.setText(_translate("SettingsDialog", "Logging"))
item.setText(_translate("SettingsDialog", "Streaming"))
item = self.section_index.item(8)
item.setText(_translate("SettingsDialog", "Logging"))
item = self.section_index.item(9)
item.setText(_translate("SettingsDialog", "Advanced"))
self.section_index.setSortingEnabled(__sortingEnabled)
self.playerPauseBackgroundVideos.setText(
Expand All @@ -464,12 +549,35 @@ def retranslateUi(self, SettingsDialog):
_translate("SettingsDialog", "Hide mouse after timeout")
)
self.label_2.setText(_translate("SettingsDialog", "(sec)"))
self.timeoutVideoInitLabel.setText(
_translate("SettingsDialog", "Video initialization timeout")
)
self.label_7.setText(_translate("SettingsDialog", "(sec)"))
self.label_4.setText(
_translate(
"SettingsDialog",
'<p>If you have a handful of free time and a desire to support this project, please <a href="https://crowdin.com/project/gridplayer">help with the translation</a>. No coding skills or special software is required!</p>',
)
)
self.streamingHLSVIAStreamlink.setText(
_translate("SettingsDialog", "Use Streamlink for HLS streams when possible")
)
self.label_8.setText(_translate("SettingsDialog", "Priority URL resolver"))
self.label_10.setText(
_translate("SettingsDialog", "Resolver priority patterns")
)
self.label_11.setText(_translate("SettingsDialog", "Wildcard syntax"))
self.streamingWildcardHelpButton.setText(_translate("SettingsDialog", "?"))
self.streamingWildcardHelp.setText(
_translate(
"SettingsDialog",
"<p><b>The asterisk</b> * matches zero or more characters.<br>\n"
"<b>The question mark</b> ? matches exactly one character.</p>\n"
"<p><i>For Host Wildcard only:</i><br>\n"
"*.example.com will match both example.com and www.example.com<br>\n"
"**.example.com will match subdomains <b>only</b></p>",
)
)
self.playlistSaveWindow.setText(
_translate("SettingsDialog", "Save window position and size")
)
Expand Down Expand Up @@ -527,3 +635,4 @@ def retranslateUi(self, SettingsDialog):


from gridplayer.widgets.language_list import LanguageList
from gridplayer.widgets.resolver_patterns_list import ResolverPatternsList
65 changes: 65 additions & 0 deletions gridplayer/models/resolver_patterns.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import re
from enum import auto
from fnmatch import fnmatch
from typing import Iterable, List, Optional
from urllib.parse import urlparse

from pydantic import BaseModel

from gridplayer.params.static import AutoName, URLResolver


class ResolverPatternType(AutoName):
WILDCARD_HOST = auto()
WILDCARD_URL = auto()
REGEX = auto()
DISABLED = auto()


class ResolverPattern(BaseModel):
pattern: str
pattern_type: ResolverPatternType
resolver: URLResolver

def is_match(self, url: str):
if self.pattern_type == ResolverPatternType.WILDCARD_HOST:
return self._match_wildcard_host(url)
elif self.pattern_type == ResolverPatternType.WILDCARD_URL:
return self._match_wildcard_url(url)
elif self.pattern_type == ResolverPatternType.REGEX:
return self._match_regex(url)

return False

def _match_wildcard_host(self, url: str) -> bool:
hostname = urlparse(url).hostname

if self.pattern.startswith("*."):
return fnmatch(hostname, self.pattern) or fnmatch(
hostname, self.pattern[2:]
)

if self.pattern.startswith("**."):
return fnmatch(hostname, self.pattern[1:])

return fnmatch(hostname, self.pattern)

def _match_wildcard_url(self, url: str) -> bool:
return fnmatch(url, self.pattern)

def _match_regex(self, url: str) -> bool:
return re.match(self.pattern, url) is not None


class ResolverPatterns(BaseModel):
__root__: List[ResolverPattern]

def __iter__(self) -> Iterable[ResolverPattern]:
return iter(self.__root__)

def get_resolver(self, url: str) -> Optional[URLResolver]:
for pattern in self.__root__:
if pattern.is_match(url):
return pattern.resolver

return None
6 changes: 6 additions & 0 deletions gridplayer/params/static.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ class SeekSyncMode(AutoName):
TIMECODE = auto()


class URLResolver(AutoName):
STREAMLINK = auto()
YT_DLP = auto()
DIRECT = auto()


class WindowState(NamedTuple):
is_maximized: bool
is_fullscreen: bool
Expand Down
Loading

0 comments on commit fc72744

Please sign in to comment.