Skip to content

Commit

Permalink
feat: add recent list support for videos/playlists
Browse files Browse the repository at this point in the history
  • Loading branch information
vzhd1701 committed Jul 29, 2022
1 parent 1ca5863 commit 48acba1
Show file tree
Hide file tree
Showing 21 changed files with 439 additions and 59 deletions.
5 changes: 5 additions & 0 deletions gridplayer/dialogs/settings.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,8 @@ def __init__(self, parent):
"player/stay_on_top": self.playerStayOnTop,
"player/show_overlay_border": self.playerShowOverlayBorder,
"player/language": self.listLanguages,
"player/recent_list_enabled": self.playerRecentList,
"player/recent_list_max_size": self.playerRecentListSize,
"playlist/grid_mode": self.gridMode,
"playlist/grid_fit": self.gridFit,
"playlist/grid_size": self.gridSize,
Expand Down Expand Up @@ -167,6 +169,7 @@ def ui_set_limits(self): # noqa: WPS213
self.logLimitSize.setRange(1, 1024 * 1024)
self.logLimitBackups.setRange(1, 1000)
self.timeoutVideoInit.setRange(1, 1000)
self.playerRecentListSize.setRange(1, 100)

self.gridSize.setRange(0, 1000)
self.gridSize.setSpecialValueText(translate("Grid Size", "Auto"))
Expand All @@ -183,6 +186,7 @@ def ui_customize_dynamic(self):
self.logLimitSize.setEnabled(self.logLimit.isChecked())
self.logLimitBackups.setEnabled(self.logLimit.isChecked())
self.streamingWildcardHelp.setVisible(False)
self.playerRecentListSize.setEnabled(self.playerRecentList.isChecked())

self.switch_page(None)

Expand All @@ -197,6 +201,7 @@ def ui_connect(self):
(self.logLimit.stateChanged, self.logLimitSize.setEnabled),
(self.logLimit.stateChanged, self.logLimitBackups.setEnabled),
(self.streamingWildcardHelpButton.clicked, self.toggle_wildcard_help),
(self.playerRecentList.stateChanged, self.playerRecentListSize.setEnabled),
)

def toggle_wildcard_help(self):
Expand Down
26 changes: 26 additions & 0 deletions gridplayer/dialogs/settings_dialog_ui.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,28 @@ def setupUi(self, SettingsDialog):
self.playerShowOverlayBorder = QtWidgets.QCheckBox(self.page_general_player)
self.playerShowOverlayBorder.setObjectName("playerShowOverlayBorder")
self.lay_section_player.addWidget(self.playerShowOverlayBorder)
self.formLayout_10 = QtWidgets.QFormLayout()
self.formLayout_10.setFieldGrowthPolicy(
QtWidgets.QFormLayout.FieldsStayAtSizeHint
)
self.formLayout_10.setObjectName("formLayout_10")
self.playerRecentList = QtWidgets.QCheckBox(self.page_general_player)
self.playerRecentList.setObjectName("playerRecentList")
self.formLayout_10.setWidget(
0, QtWidgets.QFormLayout.LabelRole, self.playerRecentList
)
self.horizontalLayout_6 = QtWidgets.QHBoxLayout()
self.horizontalLayout_6.setObjectName("horizontalLayout_6")
self.playerRecentListSize = QtWidgets.QSpinBox(self.page_general_player)
self.playerRecentListSize.setObjectName("playerRecentListSize")
self.horizontalLayout_6.addWidget(self.playerRecentListSize)
self.label_16 = QtWidgets.QLabel(self.page_general_player)
self.label_16.setObjectName("label_16")
self.horizontalLayout_6.addWidget(self.label_16)
self.formLayout_10.setLayout(
0, QtWidgets.QFormLayout.FieldRole, self.horizontalLayout_6
)
self.lay_section_player.addLayout(self.formLayout_10)
self.section_timeouts = QtWidgets.QLabel(self.page_general_player)
font = QtGui.QFont()
font.setBold(True)
Expand Down Expand Up @@ -593,6 +615,10 @@ def retranslateUi(self, SettingsDialog):
self.playerShowOverlayBorder.setText(
_translate("SettingsDialog", "Show overlay border for active video")
)
self.playerRecentList.setText(
_translate("SettingsDialog", "Enable recent list, maximum size")
)
self.label_16.setText(_translate("SettingsDialog", "(items)"))
self.section_timeouts.setText(_translate("SettingsDialog", "Timeouts"))
self.timeoutOverlayFlag.setText(
_translate("SettingsDialog", "Hide overlay after timeout")
Expand Down
63 changes: 63 additions & 0 deletions gridplayer/models/recent_list.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
from pathlib import Path
from typing import Generic, Iterable, List, Optional, TypeVar, Union

from pydantic import parse_obj_as

from gridplayer.models.video_uri import VideoURI

T = TypeVar("T")

IN_URI = Union[str, VideoURI]
IN_PATH = Union[str, Path]


class RecentList(Generic[T]):
def __init__(self):
self._list: List[T] = []

def __bool__(self) -> bool:
return bool(self._list)

def __iter__(self) -> Iterable[T]:
return iter(self._list)

def __len__(self):
return len(self._list)

def add(self, items: List[T]) -> None:
for item in reversed(items):
if item in self._list:
self._list.remove(item)
self._list.insert(0, item)

def truncate(self, limit: int) -> None:
if len(self._list) > limit:
self._list = self._list[:limit]


class RecentListVideos(RecentList[VideoURI]):
def __init__(self, uris: Optional[List[IN_URI]] = None):
super().__init__()

if uris is None:
return

for uri in uris:
if isinstance(uri, str):
try:
uri = parse_obj_as(VideoURI, uri)
except ValueError:
continue

self._list.append(uri)


class RecentListPlaylists(RecentList[Path]):
def __init__(self, paths: Optional[List[IN_PATH]] = None):
super().__init__()

if paths is None:
return

for path in paths:
self._list.append(Path(path))
48 changes: 3 additions & 45 deletions gridplayer/models/video.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,10 @@
from pathlib import Path
from typing import Iterable, List, Optional, Union
from typing import Iterable, List, Optional
from uuid import uuid4

from pydantic import ( # noqa: WPS450
UUID4,
AnyUrl,
BaseModel,
Field,
FilePath,
PydanticValueError,
ValidationError,
confloat,
)
from pydantic import UUID4, BaseModel, Field, ValidationError, confloat # noqa: WPS450
from pydantic.color import Color

from gridplayer.params.extensions import SUPPORTED_MEDIA_EXT
from gridplayer.models.video_uri import AbsoluteFilePath, VideoURI, VideoURL
from gridplayer.params.static import AudioChannelMode, VideoAspect, VideoRepeat
from gridplayer.settings import default_field

Expand All @@ -24,38 +14,6 @@
MAX_RATE = 12


class VideoURL(AnyUrl):
allowed_schemes = {"http", "https", "rtp", "rtsp", "udp", "mms", "mmsh"}
max_length = 2083


class PathNotAbsoluteError(PydanticValueError):
code = "path.not_absolute"
msg_template = 'path "{path}" is not absolute'


class PathExtensionNotSupportedError(PydanticValueError):
code = "path.ext_not_supported"
msg_template = 'path extension "{path}" is not supported'


class AbsoluteFilePath(FilePath):
@classmethod
def validate(cls, path: Path) -> Path:
super().validate(path)

if not path.is_absolute():
raise PathNotAbsoluteError(path=path)

if path.suffix[1:].lower() not in SUPPORTED_MEDIA_EXT:
raise PathExtensionNotSupportedError(path=path)

return path


VideoURI = Union[VideoURL, AbsoluteFilePath]


class Video(BaseModel):
id: UUID4 = Field(default_factory=uuid4)
uri: VideoURI
Expand Down
38 changes: 38 additions & 0 deletions gridplayer/models/video_uri.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from pathlib import Path
from typing import Union

from pydantic import AnyUrl, FilePath, PydanticValueError

from gridplayer.params.extensions import SUPPORTED_MEDIA_EXT


class VideoURL(AnyUrl):
allowed_schemes = {"http", "https", "rtp", "rtsp", "udp", "mms", "mmsh"}
max_length = 2083


class PathNotAbsoluteError(PydanticValueError):
code = "path.not_absolute"
msg_template = 'path "{path}" is not absolute'


class PathExtensionNotSupportedError(PydanticValueError):
code = "path.ext_not_supported"
msg_template = 'path extension "{path}" is not supported'


class AbsoluteFilePath(FilePath):
@classmethod
def validate(cls, path: Path) -> Path:
super().validate(path)

if not path.is_absolute():
raise PathNotAbsoluteError(path=path)

if path.suffix[1:].lower() not in SUPPORTED_MEDIA_EXT:
raise PathExtensionNotSupportedError(path=path)

return path


VideoURI = Union[VideoURL, AbsoluteFilePath]
26 changes: 25 additions & 1 deletion gridplayer/player/managers/actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -1128,12 +1128,26 @@
"func": "add_from_clipboard",
"enable_if": "is_clipboard_full",
},
"Add - Recent Videos": {
"title": translate("Actions", "Recent"),
"icon": "add-recent",
"menu_generator": "menu_generator_recent_videos",
"enable_if": "is_any_recent_videos",
"show_if": "is_recent_list_enabled",
},
"Open Playlist": {
"title": translate("Actions", "Open Playlist"),
"key": "Ctrl+O",
"icon": "open-playlist",
"func": "open_playlist",
},
"Open Playlist (Recent)": {
"title": translate("Actions", "Open Playlist (Recent)"),
"icon": "add-recent",
"menu_generator": "menu_generator_recent_playlists",
"enable_if": "is_any_recent_playlists",
"show_if": "is_recent_list_enabled",
},
"Save Playlist": {
"title": translate("Actions", "Save Playlist"),
"key": "Ctrl+S",
Expand Down Expand Up @@ -1199,6 +1213,9 @@ def is_skipped(self) -> bool:
if self.show_if and not self.show_if():
return True

if self.enable_if and self.menu_generator:
return False

# skip empty submenus
return bool(self.menu_generator and not self.menu_generator())

Expand All @@ -1214,7 +1231,7 @@ def adapt(self):
if self.value_getter is not None:
self.setText(self.value_template.replace("%v", self.value_getter()))

if self.menu_generator is not None:
if self.is_enabled and self.menu_generator:
self._generate_submenu()

elif self.check_if is not None:
Expand All @@ -1227,6 +1244,10 @@ def _generate_submenu(self):
generated_menu = CustomMenu()

for a in actions:
if a == "---":
generated_menu.addSeparator()
continue

if a.is_skipped:
continue

Expand Down Expand Up @@ -1258,6 +1279,9 @@ def _make_actions(self) -> Dict[str, QDynamicAction]:
return actions

def _make_action(self, cmd):
if cmd == "---":
return cmd

action = QDynamicAction(text=cmd["title"], parent=self.parent())

if cmd.get("icon"):
Expand Down
3 changes: 2 additions & 1 deletion gridplayer/player/managers/add_videos.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@
from PyQt5.QtWidgets import QFileDialog, qApp

from gridplayer.dialogs.add_urls import QAddURLsDialog
from gridplayer.models.video import VideoURL, filter_video_uris
from gridplayer.models.video import filter_video_uris
from gridplayer.models.video_uri import VideoURL
from gridplayer.params.extensions import (
SUPPORTED_AUDIO_EXT,
SUPPORTED_MEDIA_EXT,
Expand Down
2 changes: 2 additions & 0 deletions gridplayer/player/managers/menu.py
Original file line number Diff line number Diff line change
Expand Up @@ -305,8 +305,10 @@
"Add - Files",
"Add - URL(s)",
"Add - Clipboard",
"Add - Recent Videos",
),
"Open Playlist",
"Open Playlist (Recent)",
"Save Playlist",
"Close Playlist",
"---",
Expand Down
12 changes: 9 additions & 3 deletions gridplayer/player/managers/playlist.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@

class PlaylistManager(ManagerBase):
playlist_closed = pyqtSignal()
playlist_loaded = pyqtSignal()
playlist_file_loaded = pyqtSignal(Path)
window_state_loaded = pyqtSignal(WindowState)
grid_state_loaded = pyqtSignal(GridState)
snapshots_loaded = pyqtSignal(dict)
Expand Down Expand Up @@ -157,15 +157,19 @@ def load_playlist_file(self, playlist_file: Path):
)
return

self.load_playlist(playlist)
if not self.load_playlist(playlist):
return

self._saved_playlist = {
"path": playlist_file,
"state": hash(self._make_playlist().dumps()),
}

self.playlist_file_loaded.emit(playlist_file)

def load_playlist(self, playlist: Playlist):
self.cmd_close_playlist()
if not self.cmd_close_playlist():
return False

self.videos_loaded.emit(playlist.videos)

Expand All @@ -184,6 +188,8 @@ def load_playlist(self, playlist: Playlist):

self.alert.emit()

return True

def check_playlist_save(self) -> bool:
if not Settings().get("playlist/track_changes"):
return True
Expand Down
Loading

0 comments on commit 48acba1

Please sign in to comment.