Skip to content

Commit 37b3e34

Browse files
committed
feat: add timecode seek sync mode
1 parent 38c8235 commit 37b3e34

File tree

17 files changed

+68384
-68073
lines changed

17 files changed

+68384
-68073
lines changed

gridplayer/dialogs/settings.py

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
from gridplayer.params.static import (
1313
SUPPORTED_LANGUAGES,
1414
GridMode,
15+
SeekSyncMode,
1516
VideoAspect,
1617
VideoDriver,
1718
VideoRepeat,
@@ -73,7 +74,7 @@ def __init__(self, parent):
7374
"playlist/save_position": self.playlistSavePosition,
7475
"playlist/save_state": self.playlistSaveState,
7576
"playlist/save_window": self.playlistSaveWindow,
76-
"playlist/seek_synced": self.playlistSeekSync,
77+
"playlist/seek_sync_mode": self.playlistSeekSyncMode,
7778
"playlist/track_changes": self.playlistTrackChanges,
7879
"video_defaults/aspect": self.videoAspect,
7980
"video_defaults/repeat": self.repeatMode,
@@ -139,6 +140,7 @@ def ui_fill(self):
139140
self.fill_logLevelVLC()
140141
self.fill_language()
141142
self.fill_streamQuality()
143+
self.fill_playlistSeekSyncMode()
142144

143145
def ui_customize_dynamic(self):
144146
self.driver_selected(self.playerVideoDriver.currentIndex())
@@ -292,6 +294,15 @@ def fill_streamQuality(self):
292294

293295
_fill_combo_box(self.streamQuality, quality_codes)
294296

297+
def fill_playlistSeekSyncMode(self):
298+
seek_modes = {
299+
SeekSyncMode.NONE: self.tr("None"),
300+
SeekSyncMode.PERCENT: self.tr("Percent"),
301+
SeekSyncMode.TIMECODE: self.tr("Timecode"),
302+
}
303+
304+
_fill_combo_box(self.playlistSeekSyncMode, seek_modes)
305+
295306
def driver_selected(self, idx):
296307
driver_id = self.playerVideoDriver.itemData(idx)
297308

gridplayer/dialogs/settings_dialog_ui.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -52,12 +52,22 @@ def setupUi(self, SettingsDialog):
5252
self.playlistSaveState = QtWidgets.QCheckBox(SettingsDialog)
5353
self.playlistSaveState.setObjectName("playlistSaveState")
5454
self.lay_section_playlist.addWidget(self.playlistSaveState)
55-
self.playlistSeekSync = QtWidgets.QCheckBox(SettingsDialog)
56-
self.playlistSeekSync.setObjectName("playlistSeekSync")
57-
self.lay_section_playlist.addWidget(self.playlistSeekSync)
5855
self.playlistTrackChanges = QtWidgets.QCheckBox(SettingsDialog)
5956
self.playlistTrackChanges.setObjectName("playlistTrackChanges")
6057
self.lay_section_playlist.addWidget(self.playlistTrackChanges)
58+
self.lay_seek_sync = QtWidgets.QHBoxLayout()
59+
self.lay_seek_sync.setObjectName("lay_seek_sync")
60+
self.playlistSeekSyncModeLabel = QtWidgets.QLabel(SettingsDialog)
61+
self.playlistSeekSyncModeLabel.setObjectName("playlistSeekSyncModeLabel")
62+
self.lay_seek_sync.addWidget(self.playlistSeekSyncModeLabel)
63+
self.playlistSeekSyncMode = QtWidgets.QComboBox(SettingsDialog)
64+
self.playlistSeekSyncMode.setObjectName("playlistSeekSyncMode")
65+
self.lay_seek_sync.addWidget(self.playlistSeekSyncMode)
66+
spacerItem = QtWidgets.QSpacerItem(
67+
40, 20, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
68+
)
69+
self.lay_seek_sync.addItem(spacerItem)
70+
self.lay_section_playlist.addLayout(self.lay_seek_sync)
6171
self.lay_left_column.addLayout(self.lay_section_playlist)
6272
self.lay_section_grid = QtWidgets.QVBoxLayout()
6373
self.lay_section_grid.setObjectName("lay_section_grid")
@@ -76,10 +86,10 @@ def setupUi(self, SettingsDialog):
7686
self.gridMode = QtWidgets.QComboBox(SettingsDialog)
7787
self.gridMode.setObjectName("gridMode")
7888
self.lay_gridMode.addWidget(self.gridMode)
79-
spacerItem = QtWidgets.QSpacerItem(
89+
spacerItem1 = QtWidgets.QSpacerItem(
8090
40, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
8191
)
82-
self.lay_gridMode.addItem(spacerItem)
92+
self.lay_gridMode.addItem(spacerItem1)
8393
self.lay_section_grid.addLayout(self.lay_gridMode)
8494
self.lay_gridSize = QtWidgets.QHBoxLayout()
8595
self.lay_gridSize.setObjectName("lay_gridSize")
@@ -89,10 +99,10 @@ def setupUi(self, SettingsDialog):
8999
self.gridSizeLabel = QtWidgets.QLabel(SettingsDialog)
90100
self.gridSizeLabel.setObjectName("gridSizeLabel")
91101
self.lay_gridSize.addWidget(self.gridSizeLabel)
92-
spacerItem1 = QtWidgets.QSpacerItem(
102+
spacerItem2 = QtWidgets.QSpacerItem(
93103
40, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
94104
)
95-
self.lay_gridSize.addItem(spacerItem1)
105+
self.lay_gridSize.addItem(spacerItem2)
96106
self.lay_section_grid.addLayout(self.lay_gridSize)
97107
self.gridFit = QtWidgets.QCheckBox(SettingsDialog)
98108
self.gridFit.setObjectName("gridFit")
@@ -115,10 +125,10 @@ def setupUi(self, SettingsDialog):
115125
self.streamQuality = QtWidgets.QComboBox(SettingsDialog)
116126
self.streamQuality.setObjectName("streamQuality")
117127
self.lay_stream_quality.addWidget(self.streamQuality)
118-
spacerItem2 = QtWidgets.QSpacerItem(
128+
spacerItem3 = QtWidgets.QSpacerItem(
119129
40, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
120130
)
121-
self.lay_stream_quality.addItem(spacerItem2)
131+
self.lay_stream_quality.addItem(spacerItem3)
122132
self.lay_section_video_defaults.addLayout(self.lay_stream_quality)
123133
self.lay_aspect = QtWidgets.QHBoxLayout()
124134
self.lay_aspect.setObjectName("lay_aspect")
@@ -128,10 +138,10 @@ def setupUi(self, SettingsDialog):
128138
self.videoAspect = QtWidgets.QComboBox(SettingsDialog)
129139
self.videoAspect.setObjectName("videoAspect")
130140
self.lay_aspect.addWidget(self.videoAspect)
131-
spacerItem3 = QtWidgets.QSpacerItem(
141+
spacerItem4 = QtWidgets.QSpacerItem(
132142
40, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
133143
)
134-
self.lay_aspect.addItem(spacerItem3)
144+
self.lay_aspect.addItem(spacerItem4)
135145
self.lay_section_video_defaults.addLayout(self.lay_aspect)
136146
self.lay_repeat = QtWidgets.QHBoxLayout()
137147
self.lay_repeat.setObjectName("lay_repeat")
@@ -141,10 +151,10 @@ def setupUi(self, SettingsDialog):
141151
self.repeatMode = QtWidgets.QComboBox(SettingsDialog)
142152
self.repeatMode.setObjectName("repeatMode")
143153
self.lay_repeat.addWidget(self.repeatMode)
144-
spacerItem4 = QtWidgets.QSpacerItem(
154+
spacerItem5 = QtWidgets.QSpacerItem(
145155
40, 0, QtWidgets.QSizePolicy.Expanding, QtWidgets.QSizePolicy.Minimum
146156
)
147-
self.lay_repeat.addItem(spacerItem4)
157+
self.lay_repeat.addItem(spacerItem5)
148158
self.lay_section_video_defaults.addLayout(self.lay_repeat)
149159
self.videoRandomLoop = QtWidgets.QCheckBox(SettingsDialog)
150160
self.videoRandomLoop.setObjectName("videoRandomLoop")
@@ -156,10 +166,10 @@ def setupUi(self, SettingsDialog):
156166
self.videoMuted.setObjectName("videoMuted")
157167
self.lay_section_video_defaults.addWidget(self.videoMuted)
158168
self.lay_left_column.addLayout(self.lay_section_video_defaults)
159-
spacerItem5 = QtWidgets.QSpacerItem(
169+
spacerItem6 = QtWidgets.QSpacerItem(
160170
0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding
161171
)
162-
self.lay_left_column.addItem(spacerItem5)
172+
self.lay_left_column.addItem(spacerItem6)
163173
self.lay_body.addLayout(self.lay_left_column)
164174
self.lay_right_column = QtWidgets.QVBoxLayout()
165175
self.lay_right_column.setObjectName("lay_right_column")
@@ -271,10 +281,10 @@ def setupUi(self, SettingsDialog):
271281
self.lay_section_misc = QtWidgets.QVBoxLayout()
272282
self.lay_section_misc.setObjectName("lay_section_misc")
273283
self.lay_right_column.addLayout(self.lay_section_misc)
274-
spacerItem6 = QtWidgets.QSpacerItem(
284+
spacerItem7 = QtWidgets.QSpacerItem(
275285
0, 0, QtWidgets.QSizePolicy.Minimum, QtWidgets.QSizePolicy.Expanding
276286
)
277-
self.lay_right_column.addItem(spacerItem6)
287+
self.lay_right_column.addItem(spacerItem7)
278288
self.lay_body.addLayout(self.lay_right_column)
279289
self.lay_body.setStretch(0, 1)
280290
self.lay_main.addLayout(self.lay_body)
@@ -325,12 +335,12 @@ def retranslateUi(self, SettingsDialog):
325335
self.playlistSaveState.setText(
326336
_translate("SettingsDialog", "Save videos playing / paused status")
327337
)
328-
self.playlistSeekSync.setText(
329-
_translate("SettingsDialog", "Synchronize seek by default")
330-
)
331338
self.playlistTrackChanges.setText(
332339
_translate("SettingsDialog", "Warn about unsaved changes")
333340
)
341+
self.playlistSeekSyncModeLabel.setText(
342+
_translate("SettingsDialog", "Seek sync mode")
343+
)
334344
self.section_grid.setText(
335345
_translate("SettingsDialog", "Default Grid Parameters")
336346
)

gridplayer/models/playlist.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77

88
from gridplayer.models.grid_state import GridState
99
from gridplayer.models.video import Video
10-
from gridplayer.params.static import WindowState
10+
from gridplayer.params.static import SeekSyncMode, WindowState
1111
from gridplayer.settings import Settings, default_field
1212

1313
logger = logging.getLogger(__name__)
@@ -17,7 +17,7 @@ class Playlist(BaseModel):
1717
grid_state: GridState = GridState()
1818
window_state: Optional[WindowState]
1919
videos: Optional[List[Video]]
20-
is_seek_synced: bool = default_field("playlist/seek_synced")
20+
seek_sync_mode: SeekSyncMode = default_field("playlist/seek_sync_mode")
2121

2222
@classmethod
2323
def read(cls, filename):

gridplayer/params/static.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ class VideoDriver(AutoName):
4545
DUMMY = auto()
4646

4747

48+
class SeekSyncMode(AutoName):
49+
NONE = auto()
50+
PERCENT = auto()
51+
TIMECODE = auto()
52+
53+
4854
class WindowState(NamedTuple):
4955
is_maximized: bool
5056
is_fullscreen: bool

gridplayer/player/managers/actions.py

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
from PyQt5.QtGui import QIcon, QKeySequence
66
from PyQt5.QtWidgets import QAction, QMenu
77

8-
from gridplayer.params.static import GridMode, VideoAspect, VideoRepeat
8+
from gridplayer.params.static import GridMode, SeekSyncMode, VideoAspect, VideoRepeat
99
from gridplayer.player.managers.base import ManagerBase
1010
from gridplayer.utils.qt import translate
1111

@@ -271,12 +271,23 @@
271271
"func": "close_playlist",
272272
"enable_if": "is_videos",
273273
},
274-
"Seek Sync": {
275-
"title": translate("Actions", "Seek Sync"),
276-
"key": "Shift+S",
277-
"icon": "seek-sync",
278-
"func": "switch_seek_synced",
279-
"check_if": "is_seek_synced",
274+
"Seek Sync (None)": {
275+
"title": translate("Actions", "None"),
276+
"icon": "empty",
277+
"func": ("set_seek_sync_mode", SeekSyncMode.NONE),
278+
"check_if": ("is_seek_sync_mode_set_to", SeekSyncMode.NONE),
279+
},
280+
"Seek Sync (Percent)": {
281+
"title": translate("Actions", "Percent"),
282+
"icon": "seek-sync-percent",
283+
"func": ("set_seek_sync_mode", SeekSyncMode.PERCENT),
284+
"check_if": ("is_seek_sync_mode_set_to", SeekSyncMode.PERCENT),
285+
},
286+
"Seek Sync (Timecode)": {
287+
"title": translate("Actions", "Timecode"),
288+
"icon": "seek-sync-time",
289+
"func": ("set_seek_sync_mode", SeekSyncMode.TIMECODE),
290+
"check_if": ("is_seek_sync_mode_set_to", SeekSyncMode.TIMECODE),
280291
},
281292
"+1%": {
282293
"title": "+1%",

gridplayer/player/managers/menu.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
"icon": "jump-to",
2828
},
2929
"Grid": {"title": translate("Actions", "Grid"), "icon": "grid"},
30+
"Seek Sync": {"title": translate("Actions", "Seek Sync"), "icon": "seek-sync"},
3031
}
3132
)
3233

@@ -81,8 +82,6 @@
8182
"Play / Pause [ALL]",
8283
(
8384
"Jump (to) [ALL]",
84-
"Seek Sync",
85-
"---",
8685
"Random",
8786
"---",
8887
"+1%",
@@ -99,6 +98,12 @@
9998
"-15s",
10099
"-30s",
101100
),
101+
(
102+
"Seek Sync",
103+
"Seek Sync (None)",
104+
"Seek Sync (Percent)",
105+
"Seek Sync (Timecode)",
106+
),
102107
(
103108
"Grid",
104109
"Rows First",

gridplayer/player/managers/playlist.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from gridplayer.models.grid_state import GridState
88
from gridplayer.models.playlist import Playlist
99
from gridplayer.models.video import filter_video_uris
10-
from gridplayer.params.static import WindowState
10+
from gridplayer.params.static import SeekSyncMode, WindowState
1111
from gridplayer.player.managers.base import ManagerBase
1212
from gridplayer.settings import Settings
1313
from gridplayer.utils.files import get_playlist_path
@@ -19,7 +19,7 @@ class PlaylistManager(ManagerBase):
1919
playlist_loaded = pyqtSignal()
2020
window_state_loaded = pyqtSignal(WindowState)
2121
grid_state_loaded = pyqtSignal(GridState)
22-
is_seek_synced_loaded = pyqtSignal(bool)
22+
seek_sync_mode_loaded = pyqtSignal(SeekSyncMode)
2323
videos_loaded = pyqtSignal(list)
2424

2525
alert = pyqtSignal()
@@ -147,7 +147,7 @@ def load_playlist(self, playlist: Playlist):
147147
if playlist.window_state is not None:
148148
self.window_state_loaded.emit(playlist.window_state)
149149

150-
self.is_seek_synced_loaded.emit(playlist.is_seek_synced)
150+
self.seek_sync_mode_loaded.emit(playlist.seek_sync_mode)
151151

152152
self.alert.emit()
153153

@@ -193,5 +193,5 @@ def _make_playlist(self):
193193
grid_state=self._ctx.grid_state,
194194
window_state=self._ctx.window_state,
195195
videos=self._ctx.video_blocks.videos,
196-
is_seek_synced=self._ctx.is_seek_synced,
196+
seek_sync_mode=self._ctx.seek_sync_mode,
197197
)

gridplayer/player/managers/video_blocks.py

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from PyQt5.QtCore import Qt, pyqtSignal
44

55
from gridplayer.models.video import Video
6+
from gridplayer.params.static import SeekSyncMode
67
from gridplayer.player.managers.base import ManagerBase
78
from gridplayer.settings import Settings
89
from gridplayer.utils.qt import qt_connect
@@ -67,13 +68,14 @@ class VideoBlocksManager(ManagerBase):
6768
seek_shift_ms = pyqtSignal(int)
6869
seek_random = pyqtSignal()
6970
seek_percent = pyqtSignal(float)
71+
seek_timecode = pyqtSignal(int)
7072

7173
close_all_signal = pyqtSignal()
7274

7375
def __init__(self, **kwargs):
7476
super().__init__(**kwargs)
7577

76-
self._ctx.is_seek_synced = Settings().get("playlist/seek_synced")
78+
self._ctx.seek_sync_mode = Settings().get("playlist/seek_sync_mode")
7779

7880
self._ctx.video_blocks = VideoBlocks()
7981

@@ -85,13 +87,14 @@ def commands(self):
8587
return {
8688
"play_pause_all": self.cmd_play_pause_all,
8789
"loop_random": self.seek_random.emit,
90+
"seek_timecode": self.seek_random.emit,
8891
"seek_shift_all": self.cmd_seek_shift_all,
8992
"seek_shift_ms_all": self.cmd_seek_shift_ms_all,
9093
"step_forward": self.cmd_step_forward,
9194
"step_backward": self.cmd_step_backward,
9295
"is_videos": lambda: bool(self._ctx.video_blocks),
93-
"is_seek_synced": lambda: self._ctx.is_seek_synced,
94-
"switch_seek_synced": self.switch_seek_synced,
96+
"is_seek_sync_mode_set_to": self.is_seek_sync_mode_set_to,
97+
"set_seek_sync_mode": self.set_seek_sync_mode,
9598
"reload_all": self.reload_videos,
9699
}
97100

@@ -117,15 +120,19 @@ def cmd_step_backward(self):
117120
self.pause_all()
118121
self.step_frame.emit(1)
119122

120-
def seek_sync(self, percent):
121-
if self._ctx.is_seek_synced:
123+
def seek_sync_percent(self, percent):
124+
if self._ctx.seek_sync_mode == SeekSyncMode.PERCENT:
122125
self.seek_percent.emit(percent)
123126

124-
def switch_seek_synced(self):
125-
self._ctx.is_seek_synced = not self._ctx.is_seek_synced
127+
def seek_sync_timecode(self, timecode):
128+
if self._ctx.seek_sync_mode == SeekSyncMode.TIMECODE:
129+
self.seek_timecode.emit(timecode)
126130

127-
def set_seek_synced(self, is_seek_synced):
128-
self._ctx.is_seek_synced = is_seek_synced
131+
def is_seek_sync_mode_set_to(self, mode):
132+
return self._ctx.seek_sync_mode == mode
133+
134+
def set_seek_sync_mode(self, mode):
135+
self._ctx.seek_sync_mode = mode
129136

130137
def pause_all(self):
131138
self.set_pause.emit(True)
@@ -192,13 +199,15 @@ def _add_video_block(self, video):
192199
qt_connect(
193200
(vb.about_to_close, self.close_single),
194201
(vb.is_paused_change, self.playing_count_change),
195-
(vb.percent_changed, self.seek_sync),
202+
(vb.seeked_percent, self.seek_sync_percent),
203+
(vb.seeked_time, self.seek_sync_timecode),
196204
(vb.destroyed, self._video_block_destroyed),
197205
(self.set_pause, vb.set_pause),
198206
(self.seek_shift, vb.seek_shift_percent),
199207
(self.seek_shift_ms, vb.seek_shift),
200208
(self.seek_random, vb.seek_random),
201209
(self.seek_percent, vb.seek_percent),
210+
(self.seek_timecode, vb.seek),
202211
(self.hide_overlay, vb.hide_overlay),
203212
(self.close_all_signal, vb.close_silently),
204213
)

0 commit comments

Comments
 (0)