boris-behav-obs 9.7.1__py3-none-any.whl → 9.7.15__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
boris/core.py CHANGED
@@ -31,41 +31,32 @@ os.environ["PATH"] = str(Path(__file__).parent / "misc") + os.pathsep + os.envir
31
31
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".")))
32
32
 
33
33
  import datetime
34
+ import gzip
34
35
  import json
36
+ import locale
35
37
  import logging
38
+ import math
36
39
  import platform
37
40
  import re
38
- import PIL.Image
39
- import PIL.ImageEnhance
40
- from PIL.ImageQt import Image
41
+ import shutil
41
42
  import subprocess
42
- import locale
43
43
  import tempfile
44
44
  import time
45
45
  import urllib.request
46
- from typing import Union, Tuple
47
- from decimal import Decimal as dec
48
- from decimal import ROUND_DOWN
49
- import gzip
46
+ import zipfile
50
47
  from collections import deque
48
+ from decimal import ROUND_DOWN
49
+ from decimal import Decimal as dec
50
+
51
51
  import matplotlib
52
- import zipfile
53
- import shutil
52
+ import PIL.Image
53
+ import PIL.ImageEnhance
54
+ from PIL.ImageQt import Image
54
55
 
55
56
  matplotlib.use("QtAgg")
56
57
 
57
- from PySide6.QtCore import (
58
- Qt,
59
- QPoint,
60
- Signal,
61
- QEvent,
62
- QDateTime,
63
- QUrl,
64
- QAbstractTableModel,
65
- QElapsedTimer,
66
- QSettings,
67
- )
68
- from PySide6.QtGui import QIcon, QPixmap, QFont, QKeyEvent, QDesktopServices, QColor, QPainter, QPolygon, QAction
58
+ from PySide6.QtCore import QAbstractTableModel, QDateTime, QElapsedTimer, QEvent, QPoint, QSettings, Qt, QUrl, Signal
59
+ from PySide6.QtGui import QAction, QColor, QDesktopServices, QFont, QIcon, QKeyEvent, QPainter, QPixmap, QPolygon
69
60
  from PySide6.QtMultimedia import QSoundEffect
70
61
  from PySide6.QtWidgets import (
71
62
  QAbstractItemView,
@@ -84,7 +75,6 @@ from PySide6.QtWidgets import (
84
75
  QTableWidgetItem,
85
76
  )
86
77
 
87
-
88
78
  from . import cmd_arguments
89
79
 
90
80
  # parse command line arguments
@@ -104,39 +94,39 @@ else:
104
94
  level=logging.INFO,
105
95
  )
106
96
 
107
- from . import utilities as util
108
-
109
- from . import dialog
110
- from . import gui_utilities
111
- from . import events_cursor
112
- from . import modifier_coding_map_creator
113
- from . import geometric_measurement
114
- from . import modifiers_coding_map
115
- from . import advanced_event_filtering
116
- from . import otx_parser
117
- from . import param_panel
118
- from . import plot_events
119
- from . import plot_spectrogram_rt
120
- from . import plot_waveform_rt
121
- from . import plot_events_rt
122
- from . import plugins
123
- from . import project_functions
124
- from . import select_observations
125
- from . import subjects_pad
126
- from . import version
127
- from . import event_operations
128
- from . import core_qrc
129
- from .core_ui import Ui_MainWindow
97
+ from . import (
98
+ advanced_event_filtering,
99
+ config_file,
100
+ core_qrc,
101
+ dialog,
102
+ event_operations,
103
+ events_cursor,
104
+ geometric_measurement,
105
+ gui_utilities,
106
+ modifier_coding_map_creator,
107
+ modifiers_coding_map,
108
+ observation_operations,
109
+ otx_parser,
110
+ param_panel,
111
+ plot_events,
112
+ plot_events_rt,
113
+ plot_spectrogram_rt,
114
+ plot_waveform_rt,
115
+ plugins,
116
+ project,
117
+ project_functions,
118
+ select_observations,
119
+ select_subj_behav,
120
+ subjects_pad,
121
+ version,
122
+ video_operations,
123
+ write_event,
124
+ )
130
125
  from . import config as cfg
131
- from . import video_operations
132
- from . import project
133
- from . import menu_options as menu_options
134
126
  from . import connections as connections
135
- from . import config_file
136
- from . import select_subj_behav
137
- from . import observation_operations
138
- from . import write_event
139
-
127
+ from . import menu_options as menu_options
128
+ from . import utilities as util
129
+ from .core_ui import Ui_MainWindow
140
130
 
141
131
  logging.debug("test")
142
132
 
@@ -187,8 +177,8 @@ class TableModel(QAbstractTableModel):
187
177
  self.observation_type = observation_type
188
178
 
189
179
  def headerData(self, section: int, orientation: Qt.Orientation, role: int):
190
- if role == Qt.DisplayRole:
191
- if orientation == Qt.Horizontal:
180
+ if role == Qt.ItemDataRole.DisplayRole:
181
+ if orientation == Qt.Orientation.Horizontal:
192
182
  return self.header[section]
193
183
  else:
194
184
  return str(section + 1)
@@ -199,8 +189,8 @@ class TableModel(QAbstractTableModel):
199
189
  def columnCount(self, parent=None):
200
190
  return len(self._data[0]) if self.rowCount() else 0
201
191
 
202
- def data(self, index, role=Qt.DisplayRole):
203
- if role == Qt.DisplayRole:
192
+ def data(self, index, role=Qt.ItemDataRole.DisplayRole):
193
+ if role == Qt.ItemDataRole.DisplayRole:
204
194
  row = index.row()
205
195
  if 0 <= row < self.rowCount():
206
196
  column = index.column()
@@ -242,7 +232,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
242
232
  current_player: int = 0 # id of the selected (left click) video player
243
233
 
244
234
  mem_media_name: str = "" # record current media name. Use to check if media changed
245
- mem_playlist_index: Union[int, None] = None
235
+ mem_playlist_index: int | None = None
246
236
  saved_state = None
247
237
  user_move_slider: bool = False
248
238
  observationId: str = "" # current observation id
@@ -358,7 +348,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
358
348
  super(MainWindow, self).__init__(parent)
359
349
  self.setupUi(self)
360
350
 
361
- self.pb_live_obs.setFocusPolicy(Qt.NoFocus)
351
+ self.pb_live_obs.setFocusPolicy(Qt.FocusPolicy.NoFocus)
362
352
 
363
353
  self.ffmpeg_bin = ffmpeg_bin
364
354
  # set icons
@@ -389,7 +379,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
389
379
 
390
380
  self.lbLogoBoris.setPixmap(QPixmap(":/logo"))
391
381
  self.lbLogoBoris.setScaledContents(False)
392
- self.lbLogoBoris.setAlignment(Qt.AlignCenter)
382
+ self.lbLogoBoris.setAlignment(Qt.AlignmentFlag.AlignCenter)
393
383
 
394
384
  self.toolBar.setEnabled(True)
395
385
 
@@ -423,13 +413,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
423
413
 
424
414
  # observation time interval
425
415
  self.lb_obs_time_interval = QLabel()
426
- self.lb_obs_time_interval.setFrameStyle(QFrame.StyledPanel)
416
+ self.lb_obs_time_interval.setFrameStyle(QFrame.Shape.StyledPanel)
427
417
  self.lb_obs_time_interval.setMinimumWidth(160)
428
418
  self.statusbar.addPermanentWidget(self.lb_obs_time_interval)
429
419
 
430
420
  # time offset
431
421
  self.lbTimeOffset = QLabel()
432
- self.lbTimeOffset.setFrameStyle(QFrame.StyledPanel)
422
+ self.lbTimeOffset.setFrameStyle(QFrame.Shape.StyledPanel)
433
423
  self.lbTimeOffset.setMinimumWidth(160)
434
424
  self.statusbar.addPermanentWidget(self.lbTimeOffset)
435
425
 
@@ -460,9 +450,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
460
450
  for w in (self.dwEvents, self.dwEthogram, self.dwSubjects):
461
451
  if self.action_block_dockwidgets.isChecked():
462
452
  w.setFloating(False)
463
- w.setFeatures(QDockWidget.NoDockWidgetFeatures)
453
+ w.setFeatures(QDockWidget.DockWidgetFeature.NoDockWidgetFeatures)
464
454
  else:
465
- w.setFeatures(QDockWidget.DockWidgetMovable | QDockWidget.DockWidgetFloatable)
455
+ w.setFeatures(QDockWidget.DockWidgetFeature.DockWidgetMovable | QDockWidget.DockWidgetFeature.DockWidgetFloatable)
466
456
 
467
457
  def advanced_event_filtering(self):
468
458
  """
@@ -534,7 +524,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
534
524
  "Removing the path of media files and image directories from the project file is irreversible.<br>"
535
525
  "Are you sure to continue?"
536
526
  ),
537
- [cfg.YES, cfg.NO],
527
+ (cfg.YES, cfg.NO),
538
528
  )
539
529
  == cfg.NO
540
530
  ):
@@ -552,13 +542,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
552
542
  dialog.MessageDialog(
553
543
  cfg.programName,
554
544
  ("Removing the path of external data files is irreversible.<br>Are you sure to continue?"),
555
- [cfg.YES, cfg.NO],
545
+ (cfg.YES, cfg.NO),
556
546
  )
557
547
  == cfg.NO
558
548
  ):
559
549
  return
560
550
 
561
- if project_functions.remove_data_files_path(self.pj, self.projectFileName):
551
+ if project_functions.remove_data_files_path(self.pj):
562
552
  self.project_changed()
563
553
 
564
554
  def set_media_files_path_relative_to_project_dir(self):
@@ -570,7 +560,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
570
560
  dialog.MessageDialog(
571
561
  cfg.programName,
572
562
  ("Are you sure to continue?"),
573
- [cfg.YES, cfg.NO],
563
+ (cfg.YES, cfg.NO),
574
564
  )
575
565
  == cfg.NO
576
566
  ):
@@ -587,7 +577,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
587
577
  dialog.MessageDialog(
588
578
  cfg.programName,
589
579
  ("Are you sure to continue?"),
590
- [cfg.YES, cfg.NO],
580
+ (cfg.YES, cfg.NO),
591
581
  )
592
582
  == cfg.NO
593
583
  ):
@@ -640,7 +630,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
640
630
  """
641
631
  handle click received from coding pad
642
632
  """
643
- q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text=behaviorCode)
633
+ q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text=behaviorCode)
634
+
644
635
  self.keyPressEvent(q)
645
636
 
646
637
  def close_signal_from_coding_pad(self, geometry, preferences):
@@ -654,7 +645,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
654
645
  """
655
646
  handle click received from subjects pad
656
647
  """
657
- q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text="#subject#" + subject)
648
+ q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text="#subject#" + subject)
658
649
  self.keyPressEvent(q)
659
650
 
660
651
  def signal_from_subjects_pad(self, event):
@@ -706,9 +697,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
706
697
  QMessageBox.warning(self, cfg.programName, "No subjects to show")
707
698
  return
708
699
  self.subjects_pad = subjects_pad.SubjectsPad(self.pj, filtered_subjects)
709
- self.subjects_pad.setWindowFlags(Qt.WindowStaysOnTopHint)
700
+ self.subjects_pad.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
710
701
  self.subjects_pad.sendEventSignal.connect(self.signal_from_subjects_pad)
711
- self.subjects_pad.clickSignal.connect(self.click_signal_from_subjects_pad)
702
+ self.subjects_pad.click_signal.connect(self.click_signal_from_subjects_pad)
712
703
  self.subjects_pad.close_signal.connect(self.close_signal_from_subjects_pad)
713
704
  self.subjects_pad.show()
714
705
 
@@ -739,7 +730,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
739
730
  text: str = "Behaviors to show in ethogram list",
740
731
  table: str = cfg.ETHOGRAM,
741
732
  behavior_type: list = cfg.STATE_EVENT_TYPES,
742
- ) -> Tuple[bool, list]:
733
+ ) -> tuple[bool, list]:
743
734
  """
744
735
  allow user to:
745
736
  filter behaviors in ethogram widget
@@ -927,7 +918,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
927
918
  w = dialog.Info_widget()
928
919
  w.lwi.setVisible(False)
929
920
  w.resize(350, 100)
930
- w.setWindowFlags(Qt.WindowStaysOnTopHint)
921
+ w.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
931
922
  w.setWindowTitle(cfg.programName)
932
923
  w.label.setText("Extracting WAV from media files...")
933
924
 
@@ -1009,7 +1000,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1009
1000
  f"You choose to visualize the {plot_type} during this observation.<br>"
1010
1001
  f"{plot_type} generation can take some time for long media, be patient"
1011
1002
  ),
1012
- [cfg.YES, cfg.NO],
1003
+ (cfg.YES, cfg.NO),
1013
1004
  )
1014
1005
  == cfg.NO
1015
1006
  ):
@@ -1027,8 +1018,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1027
1018
 
1028
1019
  self.spectro = plot_spectrogram_rt.Plot_spectrogram_RT()
1029
1020
 
1030
- self.spectro.setWindowFlags(Qt.WindowStaysOnTopHint)
1031
- self.spectro.setWindowFlags(self.spectro.windowFlags() & ~Qt.WindowMinimizeButtonHint)
1021
+ self.spectro.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
1022
+ self.spectro.setWindowFlags(self.spectro.windowFlags() & ~Qt.WindowType.WindowMinimizeButtonHint)
1032
1023
 
1033
1024
  self.spectro.interval = self.spectrogram_time_interval
1034
1025
  self.spectro.cursor_color = cfg.REALTIME_PLOT_CURSOR_COLOR
@@ -1048,8 +1039,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1048
1039
  self,
1049
1040
  cfg.programName,
1050
1041
  f"Error in spectrogram generation: {r['error']}",
1051
- QMessageBox.Ok | QMessageBox.Default,
1052
- QMessageBox.NoButton,
1042
+ QMessageBox.StandardButton.Ok,
1043
+ QMessageBox.StandardButton.NoButton,
1053
1044
  )
1054
1045
  del self.spectro
1055
1046
  return
@@ -1100,7 +1091,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1100
1091
  "You choose to visualize the waveform during this observation.<br>"
1101
1092
  "The waveform generation can take some time for long media, be patient"
1102
1093
  ),
1103
- [cfg.YES, cfg.NO],
1094
+ (cfg.YES, cfg.NO),
1104
1095
  )
1105
1096
  == cfg.NO
1106
1097
  ):
@@ -1118,7 +1109,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1118
1109
 
1119
1110
  self.waveform = plot_waveform_rt.Plot_waveform_RT()
1120
1111
 
1121
- self.waveform.setWindowFlags(Qt.WindowStaysOnTopHint)
1112
+ self.waveform.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
1122
1113
  self.waveform.setWindowFlags(self.waveform.windowFlags() & ~Qt.WindowMinimizeButtonHint)
1123
1114
 
1124
1115
  self.waveform.interval = self.spectrogram_time_interval
@@ -1131,8 +1122,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1131
1122
  self,
1132
1123
  cfg.programName,
1133
1124
  f"Error in waveform generation: {r['error']}",
1134
- QMessageBox.Ok | QMessageBox.Default,
1135
- QMessageBox.NoButton,
1125
+ QMessageBox.StandardButton.Ok,
1126
+ QMessageBox.StandardButton.NoButton,
1136
1127
  )
1137
1128
  del self.waveform
1138
1129
  return
@@ -1155,7 +1146,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1155
1146
 
1156
1147
  self.plot_events = plot_events_rt.Plot_events_RT()
1157
1148
 
1158
- self.plot_events.setWindowFlags(Qt.WindowStaysOnTopHint)
1149
+ self.plot_events.setWindowFlags(Qt.WindowType.WindowStaysOnTopHint)
1159
1150
  self.plot_events.setWindowFlags(self.plot_events.windowFlags() & ~Qt.WindowMinimizeButtonHint)
1160
1151
 
1161
1152
  self.plot_events.groupby = "behaviors"
@@ -1294,8 +1285,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1294
1285
  self,
1295
1286
  cfg.programName,
1296
1287
  "No project found",
1297
- QMessageBox.Ok | QMessageBox.Default,
1298
- QMessageBox.NoButton,
1288
+ QMessageBox.StandardButton.Ok,
1289
+ QMessageBox.StandardButton.NoButton,
1299
1290
  )
1300
1291
  return
1301
1292
 
@@ -1310,7 +1301,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1310
1301
  f"with the same name (<b>{behav_coding_map['name']}</b>).<br>"
1311
1302
  "What do you want to do?"
1312
1303
  ),
1313
- ["Replace the coding map", cfg.CANCEL],
1304
+ ("Replace the coding map", cfg.CANCEL),
1314
1305
  )
1315
1306
  if response == cfg.CANCEL:
1316
1307
  return
@@ -1510,15 +1501,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1510
1501
  logging.debug("previous media file")
1511
1502
 
1512
1503
  if self.playerType == cfg.MEDIA:
1513
- if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
1514
- return
1504
+ # if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
1505
+ # self.seek_mediaplayer(dec(0))
1506
+ # return
1515
1507
 
1516
1508
  # check if media not first media
1517
1509
  if self.dw_player[0].player.playlist_pos > 0:
1518
1510
  self.dw_player[0].player.playlist_prev()
1519
1511
 
1520
1512
  elif self.dw_player[0].player.playlist_count == 1:
1521
- self.statusbar.showMessage("There is only one media file", 5000)
1513
+ self.seek_mediaplayer(dec(0))
1514
+ # self.statusbar.showMessage("There is only one media file", 5000)
1522
1515
 
1523
1516
  if hasattr(self, "spectro"):
1524
1517
  self.spectro.memChunk = -1
@@ -1665,7 +1658,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1665
1658
  time.sleep(0.3) # required for correct frame number
1666
1659
 
1667
1660
  dw.frame_viewer.setPixmap(
1668
- util.pil2pixmap(dw.player.screenshot_raw()).scaled(dw.frame_viewer.size(), Qt.KeepAspectRatio, Qt.SmoothTransformation)
1661
+ util.pil2pixmap(dw.player.screenshot_raw()).scaled(
1662
+ dw.frame_viewer.size(), Qt.AspectRatioMode.KeepAspectRatio, Qt.TransformationMode.SmoothTransformation
1663
+ )
1669
1664
  )
1670
1665
 
1671
1666
  if self.playerType == cfg.IMAGES:
@@ -1674,8 +1669,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1674
1669
  None,
1675
1670
  cfg.programName,
1676
1671
  ("The picture directory has changed since the creation of observation."),
1677
- QMessageBox.Ok | QMessageBox.Default,
1678
- QMessageBox.NoButton,
1672
+ QMessageBox.StandardButton.Ok,
1673
+ QMessageBox.StandardButton.NoButton,
1679
1674
  )
1680
1675
  return
1681
1676
 
@@ -1813,7 +1808,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1813
1808
  self,
1814
1809
  "Select a directory to save the frames",
1815
1810
  os.path.expanduser("~"),
1816
- options=QFileDialog.ShowDirsOnly,
1811
+ options=QFileDialog.Option.ShowDirsOnly,
1817
1812
  )
1818
1813
  if not output_dir:
1819
1814
  return
@@ -2195,8 +2190,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2195
2190
  None,
2196
2191
  cfg.programName,
2197
2192
  ("This function is not yet implemented"),
2198
- QMessageBox.Ok | QMessageBox.Default,
2199
- QMessageBox.NoButton,
2193
+ QMessageBox.StandardButton.Ok,
2194
+ QMessageBox.StandardButton.NoButton,
2200
2195
  )
2201
2196
 
2202
2197
  return
@@ -2287,7 +2282,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2287
2282
  self.tv_events.setModel(model)
2288
2283
 
2289
2284
  # column width
2290
- self.tv_events.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
2285
+ self.tv_events.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Interactive)
2291
2286
 
2292
2287
  def load_tw_events(self, obs_id) -> None:
2293
2288
  """
@@ -2551,8 +2546,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2551
2546
  None,
2552
2547
  cfg.programName,
2553
2548
  ("This function is not available for observations with events that do not have timestamp"),
2554
- QMessageBox.Ok | QMessageBox.Default,
2555
- QMessageBox.NoButton,
2549
+ QMessageBox.StandardButton.Ok,
2550
+ QMessageBox.StandardButton.NoButton,
2556
2551
  )
2557
2552
  return
2558
2553
 
@@ -2564,7 +2559,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2564
2559
  self,
2565
2560
  "Choose a directory to save the plots",
2566
2561
  os.path.expanduser("~"),
2567
- options=QFileDialog.ShowDirsOnly,
2562
+ options=QFileDialog.Option.ShowDirsOnly,
2568
2563
  )
2569
2564
 
2570
2565
  if not plot_directory:
@@ -2667,8 +2662,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2667
2662
  None,
2668
2663
  cfg.programName,
2669
2664
  ("The duration of one or more observation is not available"),
2670
- QMessageBox.Ok | QMessageBox.Default,
2671
- QMessageBox.NoButton,
2665
+ QMessageBox.StandardButton.Ok,
2666
+ QMessageBox.StandardButton.NoButton,
2672
2667
  )
2673
2668
  return
2674
2669
 
@@ -2680,8 +2675,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2680
2675
  None,
2681
2676
  cfg.programName,
2682
2677
  ("This function is not available for observations with events that do not have timestamp"),
2683
- QMessageBox.Ok | QMessageBox.Default,
2684
- QMessageBox.NoButton,
2678
+ QMessageBox.StandardButton.Ok,
2679
+ QMessageBox.StandardButton.NoButton,
2685
2680
  )
2686
2681
  return
2687
2682
 
@@ -2716,7 +2711,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2716
2711
  self,
2717
2712
  "Choose a directory to save the plots",
2718
2713
  os.path.expanduser("~"),
2719
- options=QFileDialog.ShowDirsOnly,
2714
+ options=QFileDialog.Option.ShowDirsOnly,
2720
2715
  )
2721
2716
  if not plot_directory:
2722
2717
  return
@@ -2784,7 +2779,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2784
2779
  dialog.MessageDialog(
2785
2780
  cfg.programName,
2786
2781
  "There is a current observation. What do you want to do?",
2787
- ["Close observation", "Continue observation"],
2782
+ ("Close observation", "Continue observation"),
2788
2783
  )
2789
2784
  == "Close observation"
2790
2785
  ):
@@ -2796,7 +2791,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2796
2791
  response = dialog.MessageDialog(
2797
2792
  cfg.programName,
2798
2793
  "What to do about the current unsaved project?",
2799
- [cfg.SAVE, cfg.DISCARD, cfg.CANCEL],
2794
+ (cfg.SAVE, cfg.DISCARD, cfg.CANCEL),
2800
2795
  )
2801
2796
 
2802
2797
  if response == cfg.SAVE:
@@ -2856,7 +2851,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2856
2851
  "In this project all the behavior and subject keys are upper case.<br>"
2857
2852
  "Do you want to convert them in lower case?"
2858
2853
  ),
2859
- [cfg.YES, cfg.NO],
2854
+ (cfg.YES, cfg.NO),
2860
2855
  )
2861
2856
  == cfg.YES
2862
2857
  ):
@@ -2908,7 +2903,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2908
2903
  dialog.MessageDialog(
2909
2904
  cfg.programName,
2910
2905
  "There is a current observation. What do you want to do?",
2911
- ["Close observation", "Continue observation"],
2906
+ ("Close observation", "Continue observation"),
2912
2907
  )
2913
2908
  == "Close observation"
2914
2909
  ):
@@ -2920,7 +2915,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2920
2915
  response = dialog.MessageDialog(
2921
2916
  cfg.programName,
2922
2917
  "What to do about the current unsaved project?",
2923
- [cfg.SAVE, cfg.DISCARD, cfg.CANCEL],
2918
+ (cfg.SAVE, cfg.DISCARD, cfg.CANCEL),
2924
2919
  )
2925
2920
 
2926
2921
  if response == cfg.SAVE:
@@ -2971,7 +2966,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2971
2966
  response = dialog.MessageDialog(
2972
2967
  cfg.programName,
2973
2968
  "There is a current observation. What do you want to do?",
2974
- ["Close observation", "Continue observation"],
2969
+ ("Close observation", "Continue observation"),
2975
2970
  )
2976
2971
  if response == "Close observation":
2977
2972
  observation_operations.close_observation(self)
@@ -2982,7 +2977,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2982
2977
  response = dialog.MessageDialog(
2983
2978
  cfg.programName,
2984
2979
  "What to do about the current unsaved project?",
2985
- [cfg.SAVE, cfg.DISCARD, cfg.CANCEL],
2980
+ (cfg.SAVE, cfg.DISCARD, cfg.CANCEL),
2986
2981
  )
2987
2982
  if response == cfg.SAVE:
2988
2983
  if self.save_project_activated() == "not saved":
@@ -3026,7 +3021,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3026
3021
  response = dialog.MessageDialog(
3027
3022
  cfg.programName,
3028
3023
  "The current observation will be closed. Do you want to continue?",
3029
- [cfg.YES, cfg.NO],
3024
+ (cfg.YES, cfg.NO),
3030
3025
  )
3031
3026
  if response == cfg.NO:
3032
3027
  self.show_data_files()
@@ -3039,7 +3034,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3039
3034
  response = dialog.MessageDialog(
3040
3035
  cfg.programName,
3041
3036
  "What to do with the current unsaved project?",
3042
- [cfg.SAVE, cfg.DISCARD, cfg.CANCEL],
3037
+ (cfg.SAVE, cfg.DISCARD, cfg.CANCEL),
3043
3038
  )
3044
3039
 
3045
3040
  if response == cfg.SAVE:
@@ -3240,7 +3235,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3240
3235
 
3241
3236
  del newProjectWindow
3242
3237
 
3243
- def save_project_json(self, project_file_name: str) -> int:
3238
+ def save_project_json(self, project_file_name: str) -> int | None:
3244
3239
  """
3245
3240
  save project to JSON file
3246
3241
  convert Decimal type in float
@@ -3306,21 +3301,21 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3306
3301
  None,
3307
3302
  cfg.programName,
3308
3303
  "Permission denied to save the project file. Try another directory",
3309
- QMessageBox.Ok | QMessageBox.Default,
3310
- QMessageBox.NoButton,
3304
+ QMessageBox.StandardButton.Ok,
3305
+ QMessageBox.StandardButton.NoButton,
3311
3306
  )
3312
3307
  self.save_project_json_started = False
3313
3308
  return 1
3314
3309
 
3315
3310
  except OSError:
3316
3311
  _, value, _ = sys.exc_info()
3317
- QMessageBox.critical(None, cfg.programName, f"Error saving the project file: {value}", QMessageBox.Ok)
3312
+ QMessageBox.critical(None, cfg.programName, f"Error saving the project file: {value}", QMessageBox.StandardButton.Ok)
3318
3313
  self.save_project_json_started = False
3319
3314
  return 4
3320
3315
 
3321
3316
  except Exception:
3322
3317
  _, value, _ = sys.exc_info()
3323
- QMessageBox.critical(None, cfg.programName, f"Error saving the project file: {value}", QMessageBox.Ok)
3318
+ QMessageBox.critical(None, cfg.programName, f"Error saving the project file: {value}", QMessageBox.StandardButton.Ok)
3324
3319
  self.save_project_json_started = False
3325
3320
  return 2
3326
3321
 
@@ -3351,7 +3346,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3351
3346
  dialog.MessageDialog(
3352
3347
  cfg.programName,
3353
3348
  f"The file {project_new_file_name} already exists.",
3354
- [cfg.CANCEL, cfg.OVERWRITE],
3349
+ (cfg.CANCEL, cfg.OVERWRITE),
3355
3350
  )
3356
3351
  == cfg.CANCEL
3357
3352
  ):
@@ -3367,7 +3362,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3367
3362
  dialog.MessageDialog(
3368
3363
  cfg.programName,
3369
3364
  f"The file {project_new_file_name} already exists.",
3370
- [cfg.CANCEL, cfg.OVERWRITE],
3365
+ (cfg.CANCEL, cfg.OVERWRITE),
3371
3366
  )
3372
3367
  == cfg.CANCEL
3373
3368
  ):
@@ -3447,7 +3442,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3447
3442
  dialog.MessageDialog(
3448
3443
  cfg.programName,
3449
3444
  f"The file {self.projectFileName} already exists.",
3450
- [cfg.CANCEL, cfg.OVERWRITE],
3445
+ (cfg.CANCEL, cfg.OVERWRITE),
3451
3446
  )
3452
3447
  == cfg.CANCEL
3453
3448
  ):
@@ -3466,7 +3461,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3466
3461
  dialog.MessageDialog(
3467
3462
  cfg.programName,
3468
3463
  f"The file {self.projectFileName} already exists.",
3469
- [cfg.CANCEL, cfg.OVERWRITE],
3464
+ (cfg.CANCEL, cfg.OVERWRITE),
3470
3465
  )
3471
3466
  == cfg.CANCEL
3472
3467
  ):
@@ -3629,8 +3624,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3629
3624
  None,
3630
3625
  cfg.programName,
3631
3626
  ("This function is not available for observations with events that do not have timestamp"),
3632
- QMessageBox.Ok | QMessageBox.Default,
3633
- QMessageBox.NoButton,
3627
+ QMessageBox.StandardButton.Ok,
3628
+ QMessageBox.StandardButton.NoButton,
3634
3629
  )
3635
3630
  return
3636
3631
 
@@ -3663,7 +3658,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3663
3658
  self,
3664
3659
  "Choose a directory to save subtitles",
3665
3660
  os.path.expanduser("~"),
3666
- options=QFileDialog.ShowDirsOnly,
3661
+ options=QFileDialog.Option.ShowDirsOnly,
3667
3662
  )
3668
3663
  if not export_dir:
3669
3664
  return
@@ -3674,22 +3669,26 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3674
3669
  None,
3675
3670
  cfg.programName,
3676
3671
  f"Error creating subtitles: {msg}",
3677
- QMessageBox.Ok | QMessageBox.Default,
3678
- QMessageBox.NoButton,
3672
+ QMessageBox.StandardButton.Ok,
3673
+ QMessageBox.StandardButton.NoButton,
3679
3674
  )
3680
3675
 
3681
3676
  def next_frame(self) -> None:
3682
3677
  """
3683
3678
  show next frame
3684
3679
  """
3680
+ # frame_step_size = self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE)
3681
+
3685
3682
  if self.playerType == cfg.IMAGES:
3686
3683
  if self.image_idx < len(self.images_list) - 1:
3687
- self.image_idx += 1
3684
+ self.image_idx += 1 # frame_step_size
3688
3685
  self.extract_frame(self.dw_player[0])
3689
3686
 
3690
3687
  if self.playerType == cfg.MEDIA:
3691
3688
  for dw in self.dw_player:
3689
+ # for _ in range(frame_step_size):
3692
3690
  dw.player.frame_step()
3691
+ # time.sleep(0.5)
3693
3692
 
3694
3693
  if self.geometric_measurements_mode:
3695
3694
  self.extract_frame(dw)
@@ -3866,7 +3865,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3866
3865
  write_event.write_event(self, event, cumulative_time)
3867
3866
  # write_event.write_event(self, event, self.getLaps())
3868
3867
 
3869
- def get_frame_index(self, player_idx: int = 0) -> Union[int, str]:
3868
+ def get_frame_index(self, player_idx: int = 0) -> int | str:
3870
3869
  """
3871
3870
  returns frame index for player player_idx
3872
3871
  """
@@ -3903,7 +3902,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3903
3902
  msg_box = QMessageBox(
3904
3903
  QMessageBox.Critical, cfg.programName, f"The code <b>{code}</b> of behavior coding map does not exist in ethogram."
3905
3904
  )
3906
- msg_box.setWindowFlags(msg_box.windowFlags() | Qt.WindowStaysOnTopHint)
3905
+ msg_box.setWindowFlags(msg_box.windowFlags() | Qt.WindowType.WindowStaysOnTopHint)
3907
3906
  msg_box.exec()
3908
3907
  return
3909
3908
 
@@ -3947,7 +3946,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3947
3946
  slider_position = self.video_slider.value() / (cfg.SLIDER_MAXIMUM - 1)
3948
3947
  if self.dw_player[0].player.duration is None:
3949
3948
  return
3950
- video_position = slider_position * self.dw_player[0].player.duration
3949
+ print(f"{slider_position=}")
3950
+
3951
+ d = self.dw_player[0].player.duration
3952
+ print(f"{d=}")
3953
+ if d is None:
3954
+ return
3955
+ video_position = slider_position * d
3956
+ # video_position = slider_position * self.dw_player[0].player.duration
3951
3957
  # self.dw_player[0].player.command("seek", str(video_position), "absolute")
3952
3958
  self.dw_player[0].player.seek(video_position, "absolute")
3953
3959
 
@@ -4058,17 +4064,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4058
4064
 
4059
4065
  Args:
4060
4066
  n_player (int): player
4061
- new_time (int): new time in ms
4067
+ new_time (float): new time in ms
4062
4068
  """
4069
+ new_time_dec = dec(new_time)
4063
4070
 
4064
4071
  if self.dw_player[n_player].player.playlist_count == 1:
4065
4072
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]:
4066
4073
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)] > 0:
4067
- if new_time < self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]:
4074
+ if new_time_dec < self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]:
4068
4075
  # hide video and mute audio if time < offset
4069
4076
  self.media_player_enabled(n_player, enable=False)
4070
4077
  else:
4071
- if new_time - dec(
4078
+ if new_time_dec - dec(
4072
4079
  self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]
4073
4080
  ) > sum(self.dw_player[n_player].media_durations):
4074
4081
  # hide video and mute audio if required time > video time + offset
@@ -4077,26 +4084,27 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4077
4084
  # show video and enable audio
4078
4085
  self.media_player_enabled(n_player, enable=True)
4079
4086
  self.seek_mediaplayer(
4080
- new_time
4087
+ new_time_dec
4081
4088
  - dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]),
4082
4089
  player=n_player,
4083
4090
  )
4084
4091
 
4085
4092
  elif self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)] < 0:
4086
- if new_time - dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]) > sum(
4087
- self.dw_player[n_player].media_durations
4088
- ):
4093
+ if new_time_dec - dec(
4094
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]
4095
+ ) > sum(self.dw_player[n_player].media_durations):
4089
4096
  # hide video and mute audio if required time > video time + offset
4090
4097
  self.media_player_enabled(n_player, enable=False)
4091
4098
  else:
4092
4099
  self.media_player_enabled(n_player, enable=True)
4093
4100
  self.seek_mediaplayer(
4094
- new_time - dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]),
4101
+ new_time_dec
4102
+ - dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]),
4095
4103
  player=n_player,
4096
4104
  )
4097
4105
 
4098
4106
  else: # no offset
4099
- self.seek_mediaplayer(new_time, player=n_player)
4107
+ self.seek_mediaplayer(new_time_dec, player=n_player)
4100
4108
 
4101
4109
  elif self.dw_player[n_player].player.playlist_count > 1:
4102
4110
  # check if new time is before the end of last video
@@ -4142,7 +4150,22 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4142
4150
  self.dw_player[n_player].player.playlist_pos = self.dw_player[n_player].player.playlist_count - 1
4143
4151
  self.seek_mediaplayer(self.dw_player[n_player].media_durations[-1], player=n_player)
4144
4152
 
4145
- def mpv_timer_out(self, value: Union[float, None] = None, scroll_slider=True):
4153
+ def activate_main_window(self):
4154
+ """
4155
+ activate main window in order to capture keyboard events
4156
+ called only in IPC mode
4157
+ """
4158
+ # check if eof reached
4159
+ # print(f"{self.dw_player[0].player.playlist_pos=}")
4160
+ # print(f"{self.dw_player[0].player.playlist_count=}")
4161
+ if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
4162
+ logging.debug("end of playlist reached")
4163
+ if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
4164
+ if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
4165
+ self.pause_video()
4166
+ self.activateWindow()
4167
+
4168
+ def mpv_timer_out(self, value: float | None = None, scroll_slider=True):
4146
4169
  """
4147
4170
  print the media current position and total length for MPV player
4148
4171
  scroll video slider to video position
@@ -4152,13 +4175,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4152
4175
  if not self.observationId:
4153
4176
  return
4154
4177
 
4155
- print("mpv timer out")
4156
-
4157
4178
  cumulative_time_pos = self.getLaps()
4158
- print(f"{cumulative_time_pos=}")
4159
4179
  # get frame index
4160
4180
  frame_idx = self.get_frame_index()
4161
- print(f"{frame_idx=}")
4162
4181
  # frame_idx = 0
4163
4182
 
4164
4183
  if value is None: # ipc mpv
@@ -4205,8 +4224,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4205
4224
  ct = self.getLaps(n_player=n_player)
4206
4225
 
4207
4226
  # sync players 2..8 if time diff >= 1 s
4208
- if abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)]))) >= 1:
4209
- self.sync_time(n_player, ct0) # self.seek_mediaplayer(ct0, n_player)
4227
+ if not math.isnan(ct) and not math.isnan(ct0):
4228
+ if (
4229
+ abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)])))
4230
+ >= 1
4231
+ ):
4232
+ self.sync_time(n_player, float(ct0)) # self.seek_mediaplayer(ct0, n_player)
4210
4233
 
4211
4234
  currentTimeOffset = dec(cumulative_time_pos + self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TIME_OFFSET])
4212
4235
 
@@ -4237,12 +4260,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4237
4260
  self.show_current_states_in_subjects_table()
4238
4261
 
4239
4262
  # current media name
4240
- print(f"{self.dw_player[0].player.playlist_pos=}")
4241
- print(f"{self.dw_player[0].player.playlist=}")
4242
-
4243
- if self.dw_player[0].player.playlist_pos is not None:
4244
- current_media_name = Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"]).name
4245
- current_playlist_index = self.dw_player[0].player.playlist_pos
4263
+ playlist = self.dw_player[0].player.playlist
4264
+ playlist_pos = self.dw_player[0].player.playlist_pos
4265
+ if playlist is not None and playlist_pos is not None and playlist:
4266
+ current_media_name = Path(playlist[playlist_pos]["filename"]).name
4267
+ current_playlist_index = playlist_pos
4246
4268
  else:
4247
4269
  current_media_name = ""
4248
4270
  current_playlist_index = None
@@ -4279,9 +4301,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4279
4301
  self.mem_media_name = current_media_name
4280
4302
  self.mem_playlist_index = current_playlist_index
4281
4303
 
4282
- playlist_length = len(self.dw_player[0].player.playlist)
4283
-
4284
4304
  # update observation info
4305
+ playlist_length = len(playlist) if playlist else 0
4285
4306
  msg = ""
4286
4307
  if self.dw_player[0].player.time_pos is not None: # check if video
4287
4308
  msg = f"Current media name: <b>{current_media_name}</b> (#{self.dw_player[0].player.playlist_pos + 1} / {playlist_length})<br>"
@@ -4315,15 +4336,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4315
4336
 
4316
4337
  self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
4317
4338
 
4318
- print(f"{msg=}")
4319
-
4320
4339
  if msg:
4321
4340
  self.lb_current_media_time.setText(msg)
4322
4341
 
4323
4342
  # set video scroll bar
4324
4343
 
4325
4344
  if scroll_slider and not self.user_move_slider:
4326
- self.video_slider.setValue(round(current_media_time_pos / current_media_duration * (cfg.SLIDER_MAXIMUM - 1)))
4345
+ if current_media_time_pos is not None and current_media_duration is not None:
4346
+ self.video_slider.setValue(round(current_media_time_pos / current_media_duration * (cfg.SLIDER_MAXIMUM - 1)))
4327
4347
 
4328
4348
  def mpv_eof_reached(self):
4329
4349
  """
@@ -4488,7 +4508,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4488
4508
  for x in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
4489
4509
  )
4490
4510
 
4491
- def choose_behavior(self, obs_key) -> Union[None, str]:
4511
+ def choose_behavior(self, obs_key) -> None | str:
4492
4512
  """
4493
4513
  fill listwidget with all behaviors coded by key
4494
4514
 
@@ -4520,7 +4540,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4520
4540
  else:
4521
4541
  return None
4522
4542
 
4523
- def choose_subject(self, subject_key) -> Union[None, str]:
4543
+ def choose_subject(self, subject_key) -> None | str:
4524
4544
  """
4525
4545
  fill listwidget with all subjects coded by key
4526
4546
 
@@ -4602,13 +4622,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4602
4622
 
4603
4623
  if self.playerType == cfg.MEDIA:
4604
4624
  # cumulative time
4625
+ if self.dw_player[n_player].player.time_pos is None:
4626
+ return dec("NaN")
4605
4627
  mem_laps = sum(self.dw_player[n_player].media_durations[0 : self.dw_player[n_player].player.playlist_pos]) + (
4606
4628
  0 if self.dw_player[n_player].player.time_pos is None else self.dw_player[n_player].player.time_pos * 1000
4607
4629
  )
4608
4630
 
4609
4631
  return dec(str(round(mem_laps / 1000, 3)))
4610
4632
 
4611
- def get_obs_time(self, n_player: int = 0) -> Tuple[dec, dec | None]:
4633
+ def get_obs_time(self, n_player: int = 0) -> tuple[dec, dec | None]:
4612
4634
  """
4613
4635
  returns time in current media and cumulative time from begining of observation
4614
4636
  do not add time offset
@@ -4621,7 +4643,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4621
4643
  """
4622
4644
 
4623
4645
  if not self.observationId:
4624
- return dec("0")
4646
+ return dec("0"), dec("0")
4625
4647
 
4626
4648
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
4627
4649
  if "finished" in self.pb_live_obs.text():
@@ -4828,7 +4850,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4828
4850
 
4829
4851
  # play / pause with space bar
4830
4852
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
4831
- if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
4853
+ # if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
4854
+ if ek == Qt.Key_Space:
4832
4855
  if self.liveObservationStarted:
4833
4856
  if (
4834
4857
  dialog.MessageDialog(cfg.programName, "Are you sure to stop the current live observation?", [cfg.YES, cfg.NO])
@@ -5457,7 +5480,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5457
5480
  check if first player ended
5458
5481
  """
5459
5482
 
5460
- print("play_video")
5483
+ logging.debug("play_video")
5461
5484
 
5462
5485
  if self.geometric_measurements_mode:
5463
5486
  return
@@ -5465,6 +5488,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5465
5488
  if self.playerType != cfg.MEDIA:
5466
5489
  return
5467
5490
 
5491
+ # check if playlist is ended. If it is ended restart playlist from beginning
5492
+ if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
5493
+ if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
5494
+ if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
5495
+ self.seek_mediaplayer(dec(0))
5496
+
5468
5497
  # check if player 1 is ended
5469
5498
  for i, dw in enumerate(self.dw_player):
5470
5499
  if (
@@ -5475,9 +5504,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5475
5504
 
5476
5505
  self.lb_player_status.clear()
5477
5506
 
5478
- # if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.VISUALIZE_WAVEFORM, False) \
5479
- # or self.pj[cfg.OBSERVATIONS][self.observationId].get(VISUALIZE_SPECTROGRAM, False):
5480
-
5481
5507
  self.statusbar.showMessage("", 0)
5482
5508
 
5483
5509
  if self.ipc_mpv_timer is not None:
@@ -5554,16 +5580,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5554
5580
 
5555
5581
  decrement = self.fast * self.play_rate if self.config_param.get(cfg.ADAPT_FAST_JUMP, cfg.ADAPT_FAST_JUMP_DEFAULT) else self.fast
5556
5582
 
5557
- new_time = (
5558
- sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5559
- + self.dw_player[0].player.playback_time
5560
- - decrement
5561
- )
5583
+ try:
5584
+ new_time = (
5585
+ sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5586
+ + self.dw_player[0].player.playback_time
5587
+ - decrement
5588
+ )
5589
+ except Exception:
5590
+ return
5562
5591
 
5563
5592
  if new_time < decrement:
5564
5593
  new_time = 0
5565
5594
 
5566
- self.seek_mediaplayer(new_time)
5595
+ self.seek_mediaplayer(dec(new_time))
5567
5596
 
5568
5597
  self.update_visualizations()
5569
5598
 
@@ -5577,13 +5606,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5577
5606
 
5578
5607
  logging.info(f"Jump forward for {increment} seconds")
5579
5608
 
5580
- new_time = (
5581
- sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5582
- + self.dw_player[0].player.playback_time
5583
- + increment
5584
- )
5609
+ try:
5610
+ new_time = (
5611
+ sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
5612
+ + self.dw_player[0].player.playback_time
5613
+ + increment
5614
+ )
5615
+ except Exception:
5616
+ return
5585
5617
 
5586
- self.seek_mediaplayer(new_time)
5618
+ self.seek_mediaplayer(dec(new_time))
5587
5619
 
5588
5620
  self.update_visualizations()
5589
5621
 
@@ -5839,6 +5871,15 @@ def main():
5839
5871
  )
5840
5872
  sys.exit()
5841
5873
 
5874
+ if sys.platform.startswith("darwin"):
5875
+ QMessageBox.warning(
5876
+ None,
5877
+ cfg.programName,
5878
+ ("This version of BORIS for macOS is still EXPERIMENTAL and should be used at your own risk."),
5879
+ QMessageBox.Ok | QMessageBox.Default,
5880
+ QMessageBox.NoButton,
5881
+ )
5882
+
5842
5883
  window.show()
5843
5884
  window.raise_() # for overlapping widget (?)
5844
5885