boris-behav-obs 9.3.4__py2.py3-none-any.whl → 9.4__py2.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/1.py ADDED
@@ -0,0 +1,45 @@
1
+ import time
2
+ from PIL import Image, ImageDraw, ImageFont
3
+ import mpv
4
+
5
+ player = mpv.MPV()
6
+
7
+ player.loop = True
8
+ player.play('/home/olivier/gdrive_sync/src/python/generate_video_test/video1.mp4')
9
+ player.wait_until_playing()
10
+
11
+ font = ImageFont.truetype('DejaVuSans.ttf', 40)
12
+
13
+ overlay = player.create_image_overlay()
14
+
15
+ img = Image.new('RGBA', (400, 150), (255, 255, 255, 0))
16
+ d = ImageDraw.Draw(img)
17
+ d.text((10, 10), 'Hello World', font=font, fill=(0, 255, 255, 128))
18
+ #d.text((10, 60), f't={ts:.3f}', font=font, fill=(255, 0, 255, 255))
19
+
20
+ pos = 100
21
+
22
+ overlay.update(img, pos=(2*pos, pos))
23
+
24
+
25
+ while not player.core_idle:
26
+ pass
27
+
28
+
29
+ '''
30
+ for pos in range(0, 500, 5):
31
+ ts = player.time_pos
32
+ if ts is None:
33
+ break
34
+
35
+ img = Image.new('RGBA', (400, 150), (255, 255, 255, 0))
36
+ d = ImageDraw.Draw(img)
37
+ d.text((10, 10), 'Hello World', font=font, fill=(0, 255, 255, 128))
38
+ d.text((10, 60), f't={ts:.3f}', font=font, fill=(255, 0, 255, 255))
39
+
40
+ overlay.update(img, pos=(2*pos, pos))
41
+ time.sleep(0.05)
42
+
43
+
44
+ overlay.remove()
45
+ '''
boris/about.py CHANGED
@@ -31,7 +31,6 @@ from . import config as cfg
31
31
  from . import utilities as util
32
32
 
33
33
 
34
- from PySide6.QtCore import qVersion
35
34
  from PySide6.QtGui import QPixmap
36
35
  from PySide6.QtWidgets import QMessageBox
37
36
 
@@ -41,8 +40,6 @@ def actionAbout_activated(self):
41
40
  About dialog
42
41
  """
43
42
 
44
- import PySide6
45
-
46
43
  programs_versions: list = ["MPV media player"]
47
44
 
48
45
  mpv_lib_version, mpv_lib_file_path, mpv_api_version = util.mpv_lib_version()
@@ -108,6 +105,7 @@ def actionAbout_activated(self):
108
105
  '<a href="https://besjournals.onlinelibrary.wiley.com/doi/full/10.1111/2041-210X.12584">DOI:10.1111/2041-210X.12584</a>'
109
106
  )
110
107
  )
108
+ """
111
109
  n = "\n"
112
110
  current_system = platform.uname()
113
111
  details = (
@@ -123,8 +121,11 @@ def actionAbout_activated(self):
123
121
  f"Memory (RAM) Total: {memory.get('total_memory', 'Not available'):.2f} Mb "
124
122
  f"Free: {memory.get('free_memory', 'Not available'):.2f} Mb\n\n"
125
123
  )
124
+ """
125
+
126
+ details = util.get_systeminfo()
126
127
 
127
- details += n.join(programs_versions)
128
+ details += "\n".join(programs_versions)
128
129
  """
129
130
  memory_in_use = f"{utilities.rss_memory_used(self.pid)} Mb" if utilities.rss_memory_used(self.pid) != -1 else "Not available"
130
131
  percent_memory_in_use = (f"({utilities.rss_memory_percent_used(self.pid):.1f} % of total memory)"
boris/config.py CHANGED
@@ -37,6 +37,7 @@ SECONDS_PER_DAY: int = 86_400
37
37
  HOUR_CUTOFF: int = 7 * 24
38
38
  DATE_CUTOFF: int = HOUR_CUTOFF * 60 * 60 # 1 week
39
39
 
40
+ # cutoff for displaying time in HH:MM:SS.zzz format
40
41
  SMART_TIME_CUTOFF_DEFAULT: int = 300
41
42
 
42
43
  # minimal project version for handling observations from images
@@ -218,6 +219,7 @@ OVERLAY = "video overlay"
218
219
 
219
220
 
220
221
  USE_EXIF_DATE = "use_exif_date"
222
+ SUBSTRACT_FIRST_EXIF_DATE = "substract_first_exif_date"
221
223
  TIME_LAPSE = "time_lapse_delay"
222
224
 
223
225
 
boris/config_file.py CHANGED
@@ -72,7 +72,7 @@ def read(self):
72
72
  if not isinstance(self.saved_state, QByteArray):
73
73
  self.saved_state = None
74
74
 
75
- logging.debug(f"saved state: {self.saved_state}")
75
+ # logging.debug(f"saved state: {self.saved_state}")
76
76
 
77
77
  self.timeFormat = cfg.HHMMSS
78
78
  try:
boris/converters.py CHANGED
@@ -22,8 +22,6 @@ This file is part of BORIS.
22
22
 
23
23
  import sys
24
24
  import json
25
- import urllib.error
26
- import urllib.parse
27
25
  import urllib.request
28
26
 
29
27
 
@@ -314,7 +312,7 @@ def load_converters_from_file_repo(self, mode: str):
314
312
  QMessageBox.critical(
315
313
  self,
316
314
  "BORIS",
317
- (f"The code of {converter_name} converter produces an error: " f"<br><b>{sys.exc_info()[1]}</b>"),
315
+ (f"The code of {converter_name} converter produces an error: <br><b>{sys.exc_info()[1]}</b>"),
318
316
  )
319
317
 
320
318
  self.tw_converters.setRowCount(self.tw_converters.rowCount() + 1)
boris/core.py CHANGED
@@ -19,34 +19,31 @@ This file is part of BORIS.
19
19
  along with this program; if not see <http://www.gnu.org/licenses/>.
20
20
 
21
21
  """
22
+ # ruff: noqa: E402
22
23
 
23
24
  import os
24
25
  import sys
26
+ import pathlib as pl
25
27
 
26
- os.environ["PATH"] = os.path.dirname(__file__) + os.sep + "misc" + os.pathsep + os.environ["PATH"]
28
+ # os.environ["PATH"] = os.path.dirname(__file__) + os.sep + "misc" + os.pathsep + os.environ["PATH"]
27
29
 
30
+ os.environ["PATH"] = str(pl.Path(__file__).parent / "misc") + os.pathsep + os.environ["PATH"]
28
31
  sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), ".")))
29
32
 
30
-
31
33
  import datetime
32
-
33
34
  import json
34
35
  import logging
35
- import pathlib as pl
36
36
  import platform
37
37
  import re
38
38
  import PIL.Image
39
39
  import PIL.ImageEnhance
40
+ from PIL.ImageQt import Image
40
41
  import subprocess
41
-
42
42
  import locale
43
43
  import tempfile
44
44
  import time
45
- import urllib.error
46
- import urllib.parse
47
45
  import urllib.request
48
46
  from typing import Union, Tuple
49
-
50
47
  from decimal import Decimal as dec
51
48
  from decimal import ROUND_DOWN
52
49
  import gzip
@@ -57,7 +54,6 @@ import shutil
57
54
 
58
55
  matplotlib.use("QtAgg")
59
56
 
60
- import PySide6
61
57
  from PySide6.QtCore import (
62
58
  Qt,
63
59
  QPoint,
@@ -66,7 +62,6 @@ from PySide6.QtCore import (
66
62
  QDateTime,
67
63
  QUrl,
68
64
  QAbstractTableModel,
69
- qVersion,
70
65
  QElapsedTimer,
71
66
  QSettings,
72
67
  )
@@ -88,7 +83,28 @@ from PySide6.QtWidgets import (
88
83
  QStyledItemDelegate,
89
84
  QTableWidgetItem,
90
85
  )
91
- from PIL.ImageQt import Image
86
+
87
+
88
+ from . import cmd_arguments
89
+
90
+ # parse command line arguments
91
+ (options, args) = cmd_arguments.parse_arguments()
92
+
93
+ # set logging parameters
94
+ if options.debug:
95
+ logging.basicConfig(
96
+ format="%(asctime)s,%(msecs)d %(module)s l.%(lineno)d %(levelname)s %(message)s",
97
+ datefmt="%H:%M:%S",
98
+ level=logging.DEBUG,
99
+ )
100
+ else:
101
+ logging.basicConfig(
102
+ format="%(asctime)s,%(msecs)d %(message)s",
103
+ datefmt="%H:%M:%S",
104
+ level=logging.INFO,
105
+ )
106
+
107
+ from . import utilities as util
92
108
 
93
109
  from . import dialog
94
110
  from . import gui_utilities
@@ -105,21 +121,15 @@ from . import plot_waveform_rt
105
121
  from . import plot_events_rt
106
122
  from . import plugins
107
123
  from . import project_functions
108
-
109
124
  from . import select_observations
110
125
  from . import subjects_pad
111
126
  from . import version
112
127
  from . import event_operations
113
- from . import cmd_arguments
114
-
115
128
  from . import core_qrc
116
129
  from .core_ui import Ui_MainWindow
117
130
  from . import config as cfg
118
131
  from . import video_operations
119
-
120
132
  from . import project
121
- from . import utilities as util
122
-
123
133
  from . import menu_options as menu_options
124
134
  from . import connections as connections
125
135
  from . import config_file
@@ -128,7 +138,7 @@ from . import observation_operations
128
138
  from . import write_event
129
139
 
130
140
 
131
- # matplotlib.pyplot.switch_backend("Qt5Agg")
141
+ logging.debug("test")
132
142
 
133
143
  __version__ = version.__version__
134
144
  __version_date__ = version.__version_date__
@@ -143,44 +153,13 @@ if util.versiontuple(platform.python_version()) < util.versiontuple(MIN_PYTHON_V
143
153
  if sys.platform == "darwin": # for MacOS
144
154
  os.environ["LC_ALL"] = "en_US.UTF-8"
145
155
 
146
- # parse command line arguments
147
- (options, args) = cmd_arguments.parse_arguments()
148
-
149
- # set logging parameters
150
- if options.debug:
151
- logging.basicConfig(
152
- format="%(asctime)s,%(msecs)d %(module)s l.%(lineno)d %(levelname)s %(message)s",
153
- datefmt="%H:%M:%S",
154
- level=logging.DEBUG,
155
- )
156
- else:
157
- logging.basicConfig(
158
- format="%(asctime)s,%(msecs)d %(message)s",
159
- datefmt="%H:%M:%S",
160
- level=logging.INFO,
161
- )
162
156
 
163
157
  if options.version:
164
158
  print(f"version {__version__} release date: {__version_date__}")
165
159
  sys.exit(0)
166
160
 
167
-
168
161
  logging.debug("BORIS started")
169
- logging.info(f"BORIS version {__version__} release date: {__version_date__}")
170
- logging.info(f"Operating system: {platform.uname().system} {platform.uname().release} {platform.uname().version}")
171
- logging.info(f"CPU: {platform.uname().machine} {platform.uname().processor}")
172
- logging.info(f"Python {platform.python_version()} ({'64-bit' if sys.maxsize > 2**32 else '32-bit'})")
173
- logging.info(f"Qt {qVersion()} - PySide {PySide6.__version__}")
174
-
175
-
176
- (r, memory) = util.mem_info()
177
- if not r:
178
- logging.info(
179
- (
180
- f"Memory (RAM) Total: {memory.get('total_memory', 'Not available'):.2f} Mb "
181
- f"Free: {memory.get('free_memory', 'Not available'):.2f} Mb"
182
- )
183
- )
162
+ logging.info(util.get_systeminfo())
184
163
 
185
164
 
186
165
  def excepthook(exception_type, exception_value, traceback_object):
@@ -308,9 +287,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
308
287
  play_rate_step: float = 0.1
309
288
  currentSubject: str = "" # contains the current subject of observation
310
289
 
311
- # FFmpeg
312
- memx = -1
313
- memy = -1
290
+ # geometric measurements
314
291
  mem_player = -1
315
292
 
316
293
  # path for ffmpeg/ffmpeg.exe program
@@ -339,6 +316,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
339
316
 
340
317
  mem_hash_obs: int = 0
341
318
 
319
+ # variables for list of observations
320
+ data: list = []
321
+ not_paired: list = []
322
+
342
323
  '''
343
324
  def add_button_menu(self, data, menu_obj):
344
325
  """
@@ -1342,21 +1323,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1342
1323
  self.pj[cfg.BEHAVIORS_CODING_MAP][idx] = dict(behav_coding_map)
1343
1324
  return
1344
1325
 
1345
- """
1346
- QMessageBox.critical(
1347
- None,
1348
- cfg.programName,
1349
- (
1350
- "The current project already contains a behaviors coding map "
1351
- f"with the same name (<b>{behav_coding_map['name']}</b>)"
1352
- ),
1353
- QMessageBox.Ok | QMessageBox.Default,
1354
- QMessageBox.NoButton,
1355
- )
1356
-
1357
- return
1358
- """
1359
-
1360
1326
  self.pj[cfg.BEHAVIORS_CODING_MAP].append(behav_coding_map)
1361
1327
  QMessageBox.information(
1362
1328
  self,
@@ -1375,7 +1341,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1375
1341
  try:
1376
1342
  last_version = urllib.request.urlopen(version_URL).read().strip().decode("utf-8")
1377
1343
  except Exception:
1378
- QMessageBox.warning(self, cfg.programName, "Can not check for updates...")
1344
+ QMessageBox.warning(self, cfg.programName, "Can not check for updates. Check your connection.")
1379
1345
  return
1380
1346
 
1381
1347
  # record check timestamp
@@ -1526,7 +1492,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1526
1492
  jt.setWindowTitle("Jump to specific time")
1527
1493
  jt.label.setText("Set the time")
1528
1494
 
1529
- if jt.exec_():
1495
+ if jt.exec():
1530
1496
  new_time = jt.time_widget.get_time()
1531
1497
  if new_time < 0:
1532
1498
  return
@@ -1704,7 +1670,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1704
1670
  QMessageBox.critical(
1705
1671
  None,
1706
1672
  cfg.programName,
1707
- ("The picture directory was changed since the creation of observation."),
1673
+ ("The picture directory has changed since the creation of observation."),
1708
1674
  QMessageBox.Ok | QMessageBox.Default,
1709
1675
  QMessageBox.NoButton,
1710
1676
  )
@@ -1718,15 +1684,13 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1718
1684
  # extract EXIF tag
1719
1685
  if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False):
1720
1686
  date_time_original = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx])
1687
+
1721
1688
  if date_time_original != -1:
1722
1689
  msg += f"<br>EXIF Date/Time Original: <b>{datetime.datetime.fromtimestamp(date_time_original):%Y-%m-%d %H:%M:%S}</b>"
1723
- else:
1724
- msg += "<br>EXIF Date/Time Original: <b>NA</b>"
1725
1690
 
1726
- if self.image_idx == 0 and date_time_original != -1:
1727
- self.image_time_ref = date_time_original
1691
+ if self.image_idx == 0:
1692
+ self.image_time_ref = date_time_original
1728
1693
 
1729
- if date_time_original != -1:
1730
1694
  if self.image_time_ref is not None:
1731
1695
  seconds_from_1st = date_time_original - self.image_time_ref
1732
1696
 
@@ -1736,6 +1700,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1736
1700
  seconds_from_1st_formated = seconds_from_1st
1737
1701
 
1738
1702
  else:
1703
+ msg += "<br>EXIF Date/Time Original: <b>NA</b>"
1739
1704
  seconds_from_1st_formated = cfg.NA
1740
1705
 
1741
1706
  msg += f"<br>Time from 1st image: <b>{seconds_from_1st_formated}</b>"
@@ -2311,11 +2276,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2311
2276
  self.tv_events.setModel(model)
2312
2277
 
2313
2278
  # column width
2314
- # https://doc.qt.io/qtforpython-5/PySide2/QtWidgets/QHeaderView.html#more
2315
- self.tv_events.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
2316
-
2317
- # self.table.setSortingEnabled(True)
2318
- # self.table.sortByColumn(0, Qt.AscendingOrder)
2279
+ self.tv_events.horizontalHeader().setSectionResizeMode(QHeaderView.Interactive)
2319
2280
 
2320
2281
  def load_tw_events(self, obs_id) -> None:
2321
2282
  """
@@ -2343,76 +2304,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2343
2304
 
2344
2305
  return
2345
2306
 
2346
- """
2347
- DISABLED tableview component is used
2348
-
2349
- logging.debug(f"begin load events from obs in tablewidget: {obs_id}")
2350
-
2351
- t1 = time.time()
2352
-
2353
- self.twEvents.clear()
2354
-
2355
- self.twEvents.setColumnCount(len(cfg.TW_EVENTS_FIELDS[self.playerType]))
2356
- self.twEvents.setHorizontalHeaderLabels([s.capitalize() for s in cfg.TW_EVENTS_FIELDS[self.playerType]])
2357
-
2358
- for idx, field in enumerate(cfg.TW_EVENTS_FIELDS[self.playerType]):
2359
- if field not in self.config_param.get(f"{self.playerType} tw fields", cfg.TW_EVENTS_FIELDS[self.playerType]):
2360
- self.twEvents.horizontalHeader().hideSection(idx)
2361
- else:
2362
- self.twEvents.horizontalHeader().showSection(idx)
2363
-
2364
- self.twEvents.setRowCount(len(self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]))
2365
- if self.filtered_behaviors or self.filtered_subjects:
2366
- self.twEvents.setRowCount(0)
2367
- row = 0
2368
-
2369
- for event_idx, event in enumerate(self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]):
2370
- if self.filtered_behaviors and event[cfg.PJ_OBS_FIELDS[self.playerType][cfg.BEHAVIOR_CODE]] not in self.filtered_behaviors:
2371
- continue
2372
-
2373
- if self.filtered_subjects and event[cfg.PJ_OBS_FIELDS[self.playerType][cfg.SUBJECT]] not in self.filtered_subjects:
2374
- continue
2375
-
2376
- if self.filtered_behaviors or self.filtered_subjects:
2377
- self.twEvents.insertRow(self.twEvents.rowCount())
2378
-
2379
- for field_type in cfg.TW_EVENTS_FIELDS[self.playerType]:
2380
- if field_type in cfg.PJ_EVENTS_FIELDS[self.playerType]:
2381
- field = event_operations.read_event_field(event, self.playerType, field_type)
2382
-
2383
- if field_type == cfg.TIME:
2384
- item = QTableWidgetItem(str(util.convertTime(self.timeFormat, field)))
2385
-
2386
- # add index of project events
2387
- item.setData(Qt.UserRole, event_idx)
2388
- self.twEvents.setItem(row, cfg.TW_OBS_FIELD[self.playerType][field_type], item)
2389
- continue
2390
-
2391
- if field_type in (cfg.IMAGE_INDEX, cfg.FRAME_INDEX):
2392
- field = str(field)
2393
-
2394
- self.twEvents.setItem(
2395
- row,
2396
- cfg.TW_OBS_FIELD[self.playerType][field_type],
2397
- QTableWidgetItem(field),
2398
- )
2399
-
2400
- else:
2401
- self.twEvents.setItem(
2402
- row,
2403
- cfg.TW_OBS_FIELD[self.playerType][field_type],
2404
- QTableWidgetItem(""),
2405
- )
2406
-
2407
- row += 1
2408
-
2409
- self.update_events_start_stop()
2410
-
2411
- print("load twevent:", time.time() - t1)
2412
-
2413
- logging.debug("end load events from obs")
2414
- """
2415
-
2416
2307
  def close_tool_windows(self):
2417
2308
  """
2418
2309
  close tool windows:
@@ -3493,7 +3384,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3493
3384
  return
3494
3385
 
3495
3386
  logging.debug(
3496
- f"{self.config_param[cfg.CHECK_PROJECT_INTEGRITY] if cfg.CHECK_PROJECT_INTEGRITY in self.config_param else "Check project integrity config NOT FOUND"=}"
3387
+ f"{self.config_param[cfg.CHECK_PROJECT_INTEGRITY] if cfg.CHECK_PROJECT_INTEGRITY in self.config_param else 'Check project integrity config NOT FOUND'=}"
3497
3388
  )
3498
3389
 
3499
3390
  if self.config_param.get(cfg.CHECK_PROJECT_INTEGRITY, True):
@@ -4707,7 +4598,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4707
4598
  self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False)
4708
4599
  and util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) != -1
4709
4600
  ):
4710
- time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) - self.image_time_ref
4601
+ time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx])
4602
+ # check if first value must be substracted
4603
+ if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.SUBSTRACT_FIRST_EXIF_DATE, True):
4604
+ time_ -= self.image_time_ref
4711
4605
 
4712
4606
  elif self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0):
4713
4607
  time_ = (self.image_idx + 1) * self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0)
@@ -4765,7 +4659,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4765
4659
  self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.USE_EXIF_DATE, False)
4766
4660
  and util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) != -1
4767
4661
  ):
4768
- time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx]) - self.image_time_ref
4662
+ time_ = util.extract_exif_DateTimeOriginal(self.images_list[self.image_idx])
4663
+ # check if first value must be substracte
4664
+ if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.SUBSTRACT_FIRST_EXIF_DATE, True):
4665
+ time_ -= self.image_time_ref
4769
4666
 
4770
4667
  elif self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0):
4771
4668
  time_ = (self.image_idx + 1) * self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.TIME_LAPSE, 0)
@@ -5791,21 +5688,54 @@ def main():
5791
5688
 
5792
5689
  locale.setlocale(locale.LC_NUMERIC, "C")
5793
5690
 
5794
- # splashscreen
5795
- # no splashscreen for Mac because it can mask the first use dialog box
5796
-
5797
- if (not options.nosplashscreen) and (sys.platform != "darwin"):
5798
- start = time.time()
5799
- splash = QSplashScreen(QPixmap(":/splash"))
5800
- splash.show()
5801
- splash.raise_()
5802
- app.processEvents()
5803
- while time.time() - start < 1:
5804
- time.sleep(0.001)
5805
-
5806
5691
  # check FFmpeg
5807
5692
  ret, msg = util.check_ffmpeg_path()
5808
5693
  if not ret:
5694
+ if sys.platform.startswith("win"):
5695
+ QMessageBox.warning(
5696
+ None,
5697
+ cfg.programName,
5698
+ "FFmpeg is not available.<br>It will be downloaded",
5699
+ QMessageBox.Ok | QMessageBox.Default,
5700
+ QMessageBox.NoButton,
5701
+ )
5702
+
5703
+ # download ffmpeg and ffprobe from https://github.com/boris-behav-obs/boris-behav-obs.github.io/releases/download/files/
5704
+ url = "https://github.com/boris-behav-obs/boris-behav-obs.github.io/releases/download/files/"
5705
+
5706
+ # search where to download ffmpeg
5707
+ ffmpeg_dir = pl.Path(__file__).parent / "misc"
5708
+
5709
+ logging.debug(f"{ffmpeg_dir=}")
5710
+
5711
+ if not ffmpeg_dir.is_dir():
5712
+ logging.info(f"Creating {ffmpeg_dir} directory")
5713
+ ffmpeg_dir.mkdir(parents=True, exist_ok=True)
5714
+
5715
+ for file_ in ("ffmpeg.exe", "ffprobe.exe"):
5716
+ local_filename = ffmpeg_dir / file_
5717
+ logging.info(f"Downloading {file_}...")
5718
+ try:
5719
+ urllib.request.urlretrieve(url + file_, local_filename)
5720
+ except Exception:
5721
+ logging.critical("The FFmpeg program can not be downloaded! Check your connection.")
5722
+ QMessageBox.warning(
5723
+ None,
5724
+ cfg.programName,
5725
+ "The FFmpeg program can not be downloaded!\nCheck your connection.",
5726
+ QMessageBox.Ok | QMessageBox.Default,
5727
+ QMessageBox.NoButton,
5728
+ )
5729
+ sys.exit(3)
5730
+
5731
+ logging.info(f"File downloaded as {local_filename}")
5732
+
5733
+ # re-test for ffmpeg
5734
+ ret, msg = util.check_ffmpeg_path()
5735
+
5736
+ if ret:
5737
+ ffmpeg_bin = msg
5738
+ else:
5809
5739
  QMessageBox.critical(
5810
5740
  None,
5811
5741
  cfg.programName,
@@ -5814,8 +5744,17 @@ def main():
5814
5744
  QMessageBox.NoButton,
5815
5745
  )
5816
5746
  sys.exit(3)
5817
- else:
5818
- ffmpeg_bin = msg
5747
+
5748
+ # splashscreen
5749
+ # no splashscreen for Mac because it can mask the first use dialog box
5750
+ if (not options.nosplashscreen) and (sys.platform != "darwin"):
5751
+ start = time.time()
5752
+ splash = QSplashScreen(QPixmap(":/splash"))
5753
+ splash.show()
5754
+ splash.raise_()
5755
+ app.processEvents()
5756
+ while time.time() - start < 1:
5757
+ time.sleep(0.001)
5819
5758
 
5820
5759
  app.setApplicationName(cfg.programName)
5821
5760
 
boris/dialog.py CHANGED
@@ -20,16 +20,15 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- from decimal import Decimal as dec
24
- from typing import Union
25
23
  import datetime as dt
24
+ from decimal import Decimal as dec
26
25
  import logging
27
26
  import math
28
27
  import pathlib as pl
29
28
  import platform
30
- import subprocess
31
29
  import sys
32
30
  import traceback
31
+ from typing import Union
33
32
 
34
33
  from PySide6.QtCore import Qt, Signal, qVersion, QRect, QTime, QDateTime, QSize
35
34
  from PySide6.QtWidgets import (
@@ -98,29 +97,11 @@ def global_error_message(exception_type, exception_value, traceback_object):
98
97
  Global error management
99
98
  save error using loggin.critical and stdout
100
99
  """
101
- import PySide6
102
-
103
- error_text: str = (
104
- f"BORIS version: {version.__version__}\n"
105
- f"OS: {platform.uname().system} {platform.uname().release} {platform.uname().version}\n"
106
- f"CPU: {platform.uname().machine} {platform.uname().processor}\n"
107
- f"Python {platform.python_version()} ({'64-bit' if sys.maxsize > 2**32 else '32-bit'})\n"
108
- f"Qt {qVersion()} - PySide {PySide6.__version__}\n"
109
- f"MPV library version: {util.mpv_lib_version()[0]}\n"
110
- f"MPV API version: {util.mpv_lib_version()[2]}\n"
111
- f"MPV library file path: {util.mpv_lib_version()[1]}\n\n"
112
- f"Error succeded at {dt.datetime.now():%Y-%m-%d %H:%M}\n\n"
113
- )
114
- error_text += "".join(traceback.format_exception(exception_type, exception_value, traceback_object))
115
100
 
116
- # system info
117
- systeminfo = ""
118
- if sys.platform.startswith("win"):
119
- systeminfo = subprocess.getoutput("systeminfo")
120
- if sys.platform.startswith("linux"):
121
- systeminfo = subprocess.getoutput("cat /etc/*rel*; uname -a")
122
-
123
- error_text += f"\n\nSystem info\n===========\n\n{systeminfo}"
101
+ error_text = "\n\nSystem info\n===========\n\n"
102
+ error_text += util.get_systeminfo()
103
+ error_text += f"Error succeded at {dt.datetime.now():%Y-%m-%d %H:%M}\n\n"
104
+ error_text += "".join(traceback.format_exception(exception_type, exception_value, traceback_object))
124
105
 
125
106
  # write to stdout
126
107
  logging.critical(error_text)
@@ -129,8 +110,6 @@ def global_error_message(exception_type, exception_value, traceback_object):
129
110
  try:
130
111
  with open(pl.Path.home() / "boris_error.log", "w") as f_error:
131
112
  f_error.write(error_text)
132
- f_error.write("\nSystem info:\n")
133
- f_error.write(systeminfo + "\n")
134
113
  except Exception:
135
114
  logging.critical(f"Impossible to write to {pl.Path.home() / 'boris_error.log'}")
136
115
 
@@ -383,7 +362,7 @@ class get_time_widget(QWidget):
383
362
  dec: time in seconds (None if no format selected)
384
363
  """
385
364
 
386
- time_sec = None
365
+ time_sec = dec("NaN")
387
366
 
388
367
  if self.rb_seconds.isChecked():
389
368
  try:
@@ -396,7 +375,7 @@ class get_time_widget(QWidget):
396
375
  QMessageBox.Ok | QMessageBox.Default,
397
376
  QMessageBox.NoButton,
398
377
  )
399
- return None
378
+ return dec("NaN")
400
379
 
401
380
  if self.rb_time.isChecked():
402
381
  time_sec = self.sb_hour.value() * 3600
@@ -408,7 +387,7 @@ class get_time_widget(QWidget):
408
387
  if self.pb_sign.text() == "-":
409
388
  time_sec = -time_sec
410
389
 
411
- return dec(time_sec).quantize(dec("0.001")) if time_sec is not None else None
390
+ return dec(time_sec).quantize(dec("0.001")) # if time_sec is not None else None
412
391
 
413
392
  def set_time(self, new_time: dec) -> None:
414
393
  """
@@ -489,7 +468,7 @@ class Ask_time(QDialog):
489
468
  )
490
469
  return
491
470
  # test time value
492
- if self.time_widget.get_time() is None:
471
+ if self.time_widget.get_time().is_nan():
493
472
  return
494
473
 
495
474
  self.accept()