boris-behav-obs 9.3.5__py2.py3-none-any.whl → 9.4.1__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/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_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
+ from pathlib import Path
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(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):
@@ -1044,8 +1023,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1044
1023
  tmp_dir = self.ffmpeg_cache_dir if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir) else tempfile.gettempdir()
1045
1024
 
1046
1025
  wav_file_path = (
1047
- pl.Path(tmp_dir)
1048
- / pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
1026
+ Path(tmp_dir) / Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
1049
1027
  )
1050
1028
 
1051
1029
  self.spectro = plot_spectrogram_rt.Plot_spectrogram_RT()
@@ -1134,8 +1112,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1134
1112
  tmp_dir = self.ffmpeg_cache_dir if self.ffmpeg_cache_dir and os.path.isdir(self.ffmpeg_cache_dir) else tempfile.gettempdir()
1135
1113
 
1136
1114
  wav_file_path = (
1137
- pl.Path(tmp_dir)
1138
- / pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
1115
+ Path(tmp_dir) / Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
1139
1116
  )
1140
1117
 
1141
1118
  self.waveform = plot_waveform_rt.Plot_waveform_RT()
@@ -1245,8 +1222,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1245
1222
 
1246
1223
  try:
1247
1224
  wav_file_path = str(
1248
- pl.Path(tmp_dir)
1249
- / pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
1225
+ Path(tmp_dir) / Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"] + ".wav").name
1250
1226
  )
1251
1227
  except Exception:
1252
1228
  return
@@ -1362,7 +1338,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1362
1338
  try:
1363
1339
  last_version = urllib.request.urlopen(version_URL).read().strip().decode("utf-8")
1364
1340
  except Exception:
1365
- QMessageBox.warning(self, cfg.programName, "Can not check for updates...")
1341
+ QMessageBox.warning(self, cfg.programName, "Can not check for updates. Check your connection.")
1366
1342
  return
1367
1343
 
1368
1344
  # record check timestamp
@@ -1394,7 +1370,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1394
1370
  return
1395
1371
 
1396
1372
  # check if a .git is present
1397
- if (pl.Path(__file__).parent.parent / pl.Path(".git")).is_dir():
1373
+ if (Path(__file__).parent.parent / Path(".git")).is_dir():
1398
1374
  QMessageBox.critical(self, cfg.programName, "A .git directory is present, BORIS cannot be automatically updated.")
1399
1375
  return
1400
1376
 
@@ -1424,7 +1400,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1424
1400
 
1425
1401
  # copy from temp dir to current BORIS dir
1426
1402
  try:
1427
- shutil.copytree(f"{temp_dir.name}/BORIS-{last_version}", pl.Path(__file__).parent.parent, dirs_exist_ok=True)
1403
+ shutil.copytree(f"{temp_dir.name}/BORIS-{last_version}", Path(__file__).parent.parent, dirs_exist_ok=True)
1428
1404
  except Exception:
1429
1405
  QMessageBox.critical(self, cfg.programName, "A problem occurred during the copy the new version of BORIS.")
1430
1406
  return
@@ -1547,15 +1523,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1547
1523
  if self.image_idx == 0:
1548
1524
  return
1549
1525
 
1550
- current_dir = pl.Path(self.images_list[self.image_idx]).parent
1526
+ current_dir = Path(self.images_list[self.image_idx]).parent
1551
1527
  for image_path in self.images_list[self.image_idx - 1 :: -1]:
1552
- if pl.Path(image_path).parent != current_dir:
1528
+ if Path(image_path).parent != current_dir:
1553
1529
  self.image_idx = self.images_list.index(image_path)
1554
1530
 
1555
1531
  # seek to first image of directory
1556
- current_dir2 = pl.Path(self.images_list[self.image_idx]).parent
1532
+ current_dir2 = Path(self.images_list[self.image_idx]).parent
1557
1533
  for image_path2 in self.images_list[self.image_idx - 1 :: -1]:
1558
- if pl.Path(image_path2).parent != current_dir2:
1534
+ if Path(image_path2).parent != current_dir2:
1559
1535
  self.image_idx = self.images_list.index(image_path2) + 1
1560
1536
  break
1561
1537
  if self.images_list.index(image_path2) == 0:
@@ -1592,9 +1568,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1592
1568
  if self.playerType == cfg.IMAGES:
1593
1569
  if len(self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.DIRECTORIES_LIST, [])) <= 1:
1594
1570
  return
1595
- current_dir = pl.Path(self.images_list[self.image_idx]).parent
1571
+ current_dir = Path(self.images_list[self.image_idx]).parent
1596
1572
  for image_path in self.images_list[self.image_idx + 1 :]:
1597
- if pl.Path(image_path).parent != current_dir:
1573
+ if Path(image_path).parent != current_dir:
1598
1574
  self.image_idx = self.images_list.index(image_path)
1599
1575
  self.extract_frame(self.dw_player[0])
1600
1576
  break
@@ -1727,8 +1703,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1727
1703
  msg += f"<br>Time from 1st image: <b>{seconds_from_1st_formated}</b>"
1728
1704
 
1729
1705
  # image path
1730
- msg += f"<br><br>Directory: <b>{pl.Path(self.images_list[self.image_idx]).parent}</b>"
1731
- msg += f"<br>File name: <b>{pl.Path(self.images_list[self.image_idx]).name}</b>"
1706
+ msg += f"<br><br>Directory: <b>{Path(self.images_list[self.image_idx]).parent}</b>"
1707
+ msg += f"<br>File name: <b>{Path(self.images_list[self.image_idx]).name}</b>"
1732
1708
  msg += f"<br><small>Image resolution: <b>{pixmap.size().width()}x{pixmap.size().height()}</b></small>"
1733
1709
 
1734
1710
  self.lb_current_media_time.setText(msg)
@@ -1840,16 +1816,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1840
1816
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.IMAGES:
1841
1817
  pixmap = QPixmap(self.images_list[self.image_idx])
1842
1818
  # draw measurements
1843
- RADIUS = 6
1819
+ # RADIUS = 6
1844
1820
  painter = QPainter()
1845
1821
  painter.begin(pixmap)
1846
1822
  for element in self.measurement_w.draw_mem.get(self.image_idx, []):
1847
1823
  painter = draw_element(painter, element)
1848
1824
  painter.end()
1849
1825
 
1850
- image_file_path = str(pl.Path(output_dir) / f"{pl.Path(self.images_list[self.image_idx]).stem}.jpg")
1826
+ image_file_path = str(Path(output_dir) / f"{Path(self.images_list[self.image_idx]).stem}.jpg")
1851
1827
  # check if file already exists
1852
- if pl.Path(image_file_path).is_file():
1828
+ if Path(image_file_path).is_file():
1853
1829
  if (
1854
1830
  dialog.MessageDialog(cfg.programName, f"The file {image_file_path} already exists.", (cfg.CANCEL, cfg.OVERWRITE))
1855
1831
  == cfg.CANCEL
@@ -1862,11 +1838,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1862
1838
  for n_player, dw in enumerate(self.dw_player):
1863
1839
  pixmap = util.pil2pixmap(dw.player.screenshot_raw())
1864
1840
 
1865
- p = pl.Path(dw.player.playlist[dw.player.playlist_pos]["filename"])
1866
- image_file_path = str(pl.Path(output_dir) / f"{p.stem}_{n_player}_{dw.player.estimated_frame_number:06}.jpg")
1841
+ p = Path(dw.player.playlist[dw.player.playlist_pos]["filename"])
1842
+ image_file_path = str(Path(output_dir) / f"{p.stem}_{n_player}_{dw.player.estimated_frame_number:06}.jpg")
1867
1843
 
1868
1844
  # draw measurements
1869
- RADIUS = 6
1845
+ # RADIUS = 6
1870
1846
  painter = QPainter()
1871
1847
  painter.begin(pixmap)
1872
1848
 
@@ -1877,7 +1853,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1877
1853
 
1878
1854
  painter.end()
1879
1855
  # check if file already exists
1880
- if pl.Path(image_file_path).is_file():
1856
+ if Path(image_file_path).is_file():
1881
1857
  if (
1882
1858
  dialog.MessageDialog(
1883
1859
  cfg.programName, f"The file {image_file_path} already exists.", (cfg.CANCEL, cfg.OVERWRITE)
@@ -1894,16 +1870,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1894
1870
  pixmap = QPixmap(self.images_list[frame_idx])
1895
1871
 
1896
1872
  # draw measurements
1897
- RADIUS = 6
1873
+ # RADIUS = 6
1898
1874
  painter = QPainter()
1899
1875
  painter.begin(pixmap)
1900
1876
  for element in self.measurement_w.draw_mem.get(frame_idx, []):
1901
1877
  painter = draw_element(painter, element)
1902
1878
  painter.end()
1903
1879
 
1904
- image_file_path = str(pl.Path(output_dir) / f"{pl.Path(self.images_list[frame_idx]).stem}.jpg")
1880
+ image_file_path = str(Path(output_dir) / f"{Path(self.images_list[frame_idx]).stem}.jpg")
1905
1881
  # check if file already exists
1906
- if pl.Path(image_file_path).is_file():
1882
+ if Path(image_file_path).is_file():
1907
1883
  if (
1908
1884
  dialog.MessageDialog(
1909
1885
  cfg.programName, f"The file {image_file_path} already exists.", (cfg.CANCEL, cfg.OVERWRITE)
@@ -1926,10 +1902,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1926
1902
 
1927
1903
  for frame_idx in d:
1928
1904
  for n_player in d[frame_idx]:
1929
- media_path = pl.Path(
1905
+ media_path = Path(
1930
1906
  self.dw_player[n_player - 1].player.playlist[self.dw_player[n_player - 1].player.playlist_pos]["filename"]
1931
1907
  )
1932
- file_name = pl.Path(f"{media_path.stem}_{element['player']}_{frame_idx:06}")
1908
+ file_name = Path(f"{media_path.stem}_{element['player']}_{frame_idx:06}")
1933
1909
 
1934
1910
  ffmpeg_command = [
1935
1911
  self.ffmpeg_bin,
@@ -1940,14 +1916,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1940
1916
  rf"select=gte(n\, {frame_idx})",
1941
1917
  "-frames:v",
1942
1918
  "1",
1943
- str(pl.Path(output_dir) / file_name.with_suffix(".jpg")),
1919
+ str(Path(output_dir) / file_name.with_suffix(".jpg")),
1944
1920
  ]
1945
1921
 
1946
1922
  p = subprocess.Popen(ffmpeg_command, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # do not use shell=True!
1947
1923
  out, error = p.communicate()
1948
1924
 
1949
- pixmap = QPixmap(str(pl.Path(output_dir) / file_name.with_suffix(".jpg")))
1950
- RADIUS = 6
1925
+ pixmap = QPixmap(str(Path(output_dir) / file_name.with_suffix(".jpg")))
1926
+ # RADIUS = 6
1951
1927
  painter = QPainter()
1952
1928
  painter.begin(pixmap)
1953
1929
 
@@ -1956,10 +1932,10 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1956
1932
 
1957
1933
  painter.end()
1958
1934
  # check if file already exists
1959
- if (pl.Path(output_dir) / file_name.with_suffix(".jpg")).is_file():
1935
+ if (Path(output_dir) / file_name.with_suffix(".jpg")).is_file():
1960
1936
  answer = dialog.MessageDialog(
1961
1937
  cfg.programName,
1962
- f"The file {pl.Path(output_dir) / file_name.with_suffix('.jpg')} already exists.",
1938
+ f"The file {Path(output_dir) / file_name.with_suffix('.jpg')} already exists.",
1963
1939
  (cfg.CANCEL, cfg.OVERWRITE, "Abort"),
1964
1940
  )
1965
1941
  if answer == cfg.CANCEL:
@@ -1967,7 +1943,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
1967
1943
  if answer == "Abort":
1968
1944
  return
1969
1945
 
1970
- pixmap.save(str(pl.Path(output_dir) / file_name.with_suffix(".jpg")), "JPG")
1946
+ pixmap.save(str(Path(output_dir) / file_name.with_suffix(".jpg")), "JPG")
1971
1947
 
1972
1948
  def resize_dw(self, dw_id):
1973
1949
  """
@@ -2441,7 +2417,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2441
2417
  """
2442
2418
  self.menuRecent_projects.clear()
2443
2419
  for project_file_path in self.recent_projects:
2444
- if pl.Path(project_file_path).is_file():
2420
+ if Path(project_file_path).is_file():
2445
2421
  action = QAction(self, visible=False, triggered=self.open_project_activated)
2446
2422
  action.setText(project_file_path)
2447
2423
  action.setVisible(True)
@@ -2772,7 +2748,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
2772
2748
  self.projectChanged = project_changed
2773
2749
  self.load_behaviors_in_twEthogram([self.pj[cfg.ETHOGRAM][x][cfg.BEHAVIOR_CODE] for x in self.pj[cfg.ETHOGRAM]])
2774
2750
  self.load_subjects_in_twSubjects([self.pj[cfg.SUBJECTS][x][cfg.SUBJECT_NAME] for x in self.pj[cfg.SUBJECTS]])
2775
- self.projectFileName = str(pl.Path(project_path).absolute())
2751
+ self.projectFileName = str(Path(project_path).absolute())
2776
2752
  self.project = True
2777
2753
  if str(self.projectFileName) not in self.recent_projects:
2778
2754
  self.recent_projects = [str(self.projectFileName)] + self.recent_projects
@@ -3356,7 +3332,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3356
3332
  project_new_file_name = os.path.splitext(os.path.splitext(project_new_file_name)[0])[0]
3357
3333
  project_new_file_name += ".boris"
3358
3334
  # check if file name with extension already exists
3359
- if pl.Path(project_new_file_name).is_file():
3335
+ if Path(project_new_file_name).is_file():
3360
3336
  if (
3361
3337
  dialog.MessageDialog(
3362
3338
  cfg.programName,
@@ -3372,7 +3348,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3372
3348
  project_new_file_name = os.path.splitext(project_new_file_name)[0]
3373
3349
  project_new_file_name += ".boris.gz"
3374
3350
  # check if file name with extension already exists
3375
- if pl.Path(project_new_file_name).is_file():
3351
+ if Path(project_new_file_name).is_file():
3376
3352
  if (
3377
3353
  dialog.MessageDialog(
3378
3354
  cfg.programName,
@@ -3452,7 +3428,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3452
3428
  self.projectFileName = os.path.splitext(os.path.splitext(self.projectFileName)[0])[0]
3453
3429
  self.projectFileName += ".boris"
3454
3430
  # check if file name with extension already exists
3455
- if pl.Path(self.projectFileName).is_file():
3431
+ if Path(self.projectFileName).is_file():
3456
3432
  if (
3457
3433
  dialog.MessageDialog(
3458
3434
  cfg.programName,
@@ -3471,7 +3447,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
3471
3447
 
3472
3448
  self.projectFileName += ".boris.gz"
3473
3449
  # check if file name with extension already exists
3474
- if pl.Path(self.projectFileName).is_file():
3450
+ if Path(self.projectFileName).is_file():
3475
3451
  if (
3476
3452
  dialog.MessageDialog(
3477
3453
  cfg.programName,
@@ -4065,14 +4041,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4065
4041
  """
4066
4042
  enable or disable video if any and audio if any
4067
4043
  """
4068
- # if video
4069
- # print(f"{n_player=} {enable=}")
4070
- # print(f"{self.dw_player[n_player].player.video_format=}")
4071
- # print(f"{self.dw_player[n_player].player.audio_bitrate=}")
4072
-
4073
- # self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.HAS_VIDEO][]
4074
- # if self.dw_player[n_player].player.playlist_pos is not None:
4075
- # print(self.dw_player[n_player].player.playlist[self.dw_player[n_player].player.playlist_pos]["filename"])
4076
4044
 
4077
4045
  if self.dw_player[n_player].player.video_format:
4078
4046
  self.dw_player[n_player].stack.setCurrentIndex(1 if not enable else 0)
@@ -4266,7 +4234,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
4266
4234
 
4267
4235
  # current media name
4268
4236
  if self.dw_player[0].player.playlist_pos is not None:
4269
- current_media_name = pl.Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"]).name
4237
+ current_media_name = Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"]).name
4270
4238
  current_playlist_index = self.dw_player[0].player.playlist_pos
4271
4239
  else:
4272
4240
  current_media_name = ""
@@ -5165,7 +5133,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
5165
5133
  ),
5166
5134
  )
5167
5135
  return
5168
- media_file_name = self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"]
5136
+ media_file_name = Path(self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"]).as_posix()
5169
5137
 
5170
5138
  time_ -= self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME][media_file_name]
5171
5139
 
@@ -5709,21 +5677,54 @@ def main():
5709
5677
 
5710
5678
  locale.setlocale(locale.LC_NUMERIC, "C")
5711
5679
 
5712
- # splashscreen
5713
- # no splashscreen for Mac because it can mask the first use dialog box
5714
-
5715
- if (not options.nosplashscreen) and (sys.platform != "darwin"):
5716
- start = time.time()
5717
- splash = QSplashScreen(QPixmap(":/splash"))
5718
- splash.show()
5719
- splash.raise_()
5720
- app.processEvents()
5721
- while time.time() - start < 1:
5722
- time.sleep(0.001)
5723
-
5724
5680
  # check FFmpeg
5725
5681
  ret, msg = util.check_ffmpeg_path()
5726
5682
  if not ret:
5683
+ if sys.platform.startswith("win"):
5684
+ QMessageBox.warning(
5685
+ None,
5686
+ cfg.programName,
5687
+ "FFmpeg is not available.<br>It will be downloaded",
5688
+ QMessageBox.Ok | QMessageBox.Default,
5689
+ QMessageBox.NoButton,
5690
+ )
5691
+
5692
+ # download ffmpeg and ffprobe from https://github.com/boris-behav-obs/boris-behav-obs.github.io/releases/download/files/
5693
+ url = "https://github.com/boris-behav-obs/boris-behav-obs.github.io/releases/download/files/"
5694
+
5695
+ # search where to download ffmpeg
5696
+ ffmpeg_dir = Path(__file__).parent / "misc"
5697
+
5698
+ logging.debug(f"{ffmpeg_dir=}")
5699
+
5700
+ if not ffmpeg_dir.is_dir():
5701
+ logging.info(f"Creating {ffmpeg_dir} directory")
5702
+ ffmpeg_dir.mkdir(parents=True, exist_ok=True)
5703
+
5704
+ for file_ in ("ffmpeg.exe", "ffprobe.exe"):
5705
+ local_filename = ffmpeg_dir / file_
5706
+ logging.info(f"Downloading {file_}...")
5707
+ try:
5708
+ urllib.request.urlretrieve(url + file_, local_filename)
5709
+ except Exception:
5710
+ logging.critical("The FFmpeg program can not be downloaded! Check your connection.")
5711
+ QMessageBox.warning(
5712
+ None,
5713
+ cfg.programName,
5714
+ "The FFmpeg program can not be downloaded!\nCheck your connection.",
5715
+ QMessageBox.Ok | QMessageBox.Default,
5716
+ QMessageBox.NoButton,
5717
+ )
5718
+ sys.exit(3)
5719
+
5720
+ logging.info(f"File downloaded as {local_filename}")
5721
+
5722
+ # re-test for ffmpeg
5723
+ ret, msg = util.check_ffmpeg_path()
5724
+
5725
+ if ret:
5726
+ ffmpeg_bin = msg
5727
+ else:
5727
5728
  QMessageBox.critical(
5728
5729
  None,
5729
5730
  cfg.programName,
@@ -5732,8 +5733,17 @@ def main():
5732
5733
  QMessageBox.NoButton,
5733
5734
  )
5734
5735
  sys.exit(3)
5735
- else:
5736
- ffmpeg_bin = msg
5736
+
5737
+ # splashscreen
5738
+ # no splashscreen for Mac because it can mask the first use dialog box
5739
+ if (not options.nosplashscreen) and (sys.platform != "darwin"):
5740
+ start = time.time()
5741
+ splash = QSplashScreen(QPixmap(":/splash"))
5742
+ splash.show()
5743
+ splash.raise_()
5744
+ app.processEvents()
5745
+ while time.time() - start < 1:
5746
+ time.sleep(0.001)
5737
5747
 
5738
5748
  app.setApplicationName(cfg.programName)
5739
5749
 
@@ -5771,7 +5781,7 @@ def main():
5771
5781
  # check project integrity
5772
5782
  # read config
5773
5783
  config_param: dict = {}
5774
- ini_file_path = pl.Path.home() / pl.Path(".boris")
5784
+ ini_file_path = Path.home() / Path(".boris")
5775
5785
  if ini_file_path.is_file():
5776
5786
  settings = QSettings(str(ini_file_path), QSettings.IniFormat)
5777
5787
  try:
boris/dialog.py CHANGED
@@ -26,7 +26,6 @@ import logging
26
26
  import math
27
27
  import pathlib as pl
28
28
  import platform
29
- import subprocess
30
29
  import sys
31
30
  import traceback
32
31
  from typing import Union
@@ -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
 
boris/observation.py CHANGED
@@ -1241,9 +1241,11 @@ class Observation(QDialog, Ui_Form):
1241
1241
  str: error message or empty string
1242
1242
  """
1243
1243
 
1244
+ logging.debug(f"check_media function for {file_path}")
1245
+
1244
1246
  media_info = util.accurate_media_analysis(self.ffmpeg_bin, file_path)
1245
1247
 
1246
- print(f"{media_info=}")
1248
+ logging.debug(f"{media_info=}")
1247
1249
 
1248
1250
  if "error" in media_info:
1249
1251
  return (True, media_info["error"])
@@ -1265,6 +1267,9 @@ class Observation(QDialog, Ui_Form):
1265
1267
  self.mediaFPS[file_path] = float(media_info["fps"])
1266
1268
  self.mediaHasVideo[file_path] = media_info["has_video"]
1267
1269
  self.mediaHasAudio[file_path] = media_info["has_audio"]
1270
+
1271
+ logging.debug(f"{file_path=}")
1272
+
1268
1273
  self.add_media_to_listview(file_path)
1269
1274
  return (False, "")
1270
1275
 
@@ -1324,6 +1329,8 @@ class Observation(QDialog, Ui_Form):
1324
1329
  if "media " in mode:
1325
1330
  file_paths, _ = fd.getOpenFileNames(self, "Add media file(s)", "", "All files (*)")
1326
1331
 
1332
+ logging.debug(f"{file_paths=}")
1333
+
1327
1334
  if file_paths:
1328
1335
  # store directory for next usage
1329
1336
  self.mem_dir = str(pl.Path(file_paths[0]).parent)
@@ -1341,9 +1348,6 @@ class Observation(QDialog, Ui_Form):
1341
1348
 
1342
1349
  for file_path in file_paths:
1343
1350
  (error, msg) = self.check_media(file_path, mode)
1344
-
1345
- print(f"{(error, msg)=}")
1346
-
1347
1351
  if error:
1348
1352
  QMessageBox.critical(self, cfg.programName, f"<b>{file_path}</b>. {msg}")
1349
1353
 
@@ -1055,6 +1055,8 @@ def new_observation(self, mode: str = cfg.NEW, obsId: str = "") -> None:
1055
1055
  }
1056
1056
 
1057
1057
  if self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_CREATION_DATE_AS_OFFSET]:
1058
+ print("\n", observationWindow.media_creation_time, "\n")
1059
+
1058
1060
  self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME] = observationWindow.media_creation_time
1059
1061
 
1060
1062
  try:
@@ -43,30 +43,6 @@ from PySide6.QtCore import Signal, QEvent, Qt
43
43
  from PySide6.QtGui import QIcon, QAction
44
44
 
45
45
 
46
- """
47
- try:
48
- # import last version of python-mpv
49
- from . import mpv2 as mpv
50
-
51
- # check if MPV API v. 1
52
- # is v. 1 use the old version of mpv.py
53
- try:
54
- if "libmpv.so.1" in mpv.sofile:
55
- from . import mpv as mpv
56
- except AttributeError:
57
- if "mpv-1.dll" in mpv.dll:
58
- from . import mpv as mpv
59
-
60
- except RuntimeError: # libmpv found but version too old
61
- from . import mpv as mpv
62
-
63
- except OSError: # libmpv not found
64
- msg = "LIBMPV library not found!\n"
65
- logging.critical(msg)
66
- sys.exit()
67
- """
68
-
69
-
70
46
  class Clickable_label(QLabel):
71
47
  """
72
48
  QLabel class for visualiziong frames for geometric measurments
boris/utilities.py CHANGED
@@ -19,54 +19,101 @@ Copyright 2012-2025 Olivier Friard
19
19
  MA 02110-1301, USA.
20
20
  """
21
21
 
22
+ from decimal import Decimal as dec
23
+ from decimal import getcontext, ROUND_DOWN
24
+ from hachoir.metadata import extractMetadata
25
+ from hachoir.parser import createParser
26
+ from shutil import copyfile
27
+ from typing import Union, Tuple
22
28
  import csv
29
+ import datetime
23
30
  import datetime as dt
31
+ import exifread
24
32
  import json
25
33
  import logging
26
34
  import math
35
+ import numpy as np
27
36
  import os
28
37
  import pathlib as pl
38
+ from PIL.ImageQt import Image
39
+ import platform
29
40
  import re
41
+ import shutil
30
42
  import subprocess
31
43
  import sys
32
44
  import urllib.parse
45
+ import urllib.request
33
46
  import wave
34
- import exifread
35
- import datetime
36
- from decimal import Decimal as dec
37
- from decimal import getcontext, ROUND_DOWN
38
- from shutil import copyfile
39
- from typing import Union, Tuple
40
47
 
41
- from hachoir.parser import createParser
42
- from hachoir.metadata import extractMetadata
43
-
44
-
45
- import numpy as np
48
+ from PySide6 import __version__ as pyside6_version
46
49
  from PySide6.QtGui import QPixmap, QImage
47
-
48
- from PIL.ImageQt import Image
50
+ from PySide6.QtCore import qVersion
49
51
 
50
52
  from . import config as cfg
53
+ from . import version
51
54
 
52
- from . import mpv2 as mpv
55
+ logger = logging.getLogger(__name__)
53
56
 
54
- """
55
57
  try:
56
58
  from . import mpv2 as mpv
59
+ except Exception:
60
+ logger.warning("MPV library not found")
57
61
 
58
- # check if MPV API v. 1
59
- # is v. 1 use the old version of mpv.py
60
- try:
61
- if "libmpv.so.1" in mpv.sofile:
62
- from . import mpv as mpv
63
- except AttributeError:
64
- if "mpv-1.dll" in mpv.dll:
65
- from . import mpv as mpv
66
-
67
- except RuntimeError: # libmpv found but version too old
68
- from . import mpv as mpv
69
- """
62
+ if sys.platform.startswith("win"):
63
+ import ctypes
64
+
65
+ ctypes.windll.user32.MessageBoxW(0, "The MPV library was not found!\nIt will be downloaded.", "BORIS", 0)
66
+
67
+ # download libmpv2.dll and ffprobe from https://github.com/boris-behav-obs/boris-behav-obs.github.io/releases/download/files/
68
+
69
+ url = "https://github.com/boris-behav-obs/boris-behav-obs.github.io/releases/download/files/"
70
+
71
+ external_files_dir = ""
72
+ # search where to download libmpv-2.dll
73
+
74
+ external_files_dir = pl.Path(__file__).parent / "misc"
75
+ if not external_files_dir.is_dir():
76
+ logger.info(f"Creating {external_files_dir} directory")
77
+ external_files_dir.mkdir(parents=True, exist_ok=True)
78
+
79
+ logger.info(f"MPV library directory: {external_files_dir}")
80
+
81
+ local_filename = external_files_dir / "libmpv-2.dll"
82
+ logger.info("Downloading libmpv-2.dll...")
83
+ try:
84
+ urllib.request.urlretrieve(url + "libmpv-2.dll", local_filename)
85
+ logger.info(f"File downloaded as {local_filename}")
86
+ except Exception:
87
+ logger.critical("The MPV library can not be downloaded! Check your connection.")
88
+ ctypes.windll.user32.MessageBoxW(0, "The MPV library can not be downloaded!\nCheck your connection.", "BORIS", 0)
89
+ sys.exit(5)
90
+ # reload package
91
+ try:
92
+ from . import mpv2 as mpv
93
+ except Exception:
94
+ logger.warning("MPV library not found after dowloading")
95
+ sys.exit(5)
96
+
97
+ elif sys.platform.startswith("linux"):
98
+ text = (
99
+ "The MPV library was not found!\nInstall it\n\n"
100
+ "With Debian/Ubuntu/Mint:\nsudo apt install libmpv2\n\n"
101
+ "With Fedora:\nsudo dnf install mpv-libs\n\n"
102
+ "With OpenSUSE:\nsudo zypper install mpv\n\n"
103
+ "Arch Linux / Manjaro:\nsudo pacman -S mpv\n\n"
104
+ )
105
+ if shutil.which("zenity") is not None:
106
+ subprocess.run(["zenity", "--error", f"--text={text}"])
107
+ elif shutil.which("kdialog"):
108
+ subprocess.run(["kdialog", "--msgbox", text])
109
+ elif shutil.which("gxmessage"):
110
+ subprocess.run(["gxmessage", text])
111
+ elif shutil.which("xmessage"):
112
+ subprocess.run(["xmessage", text])
113
+
114
+ sys.exit(5)
115
+ else:
116
+ sys.exit(5)
70
117
 
71
118
 
72
119
  def extract_exif_DateTimeOriginal(file_path: str) -> int:
@@ -110,10 +157,10 @@ def extract_video_creation_date(file_path: str) -> int | None:
110
157
  returns the timestamp of the media creation date time with Hachoir
111
158
  """
112
159
 
113
- logging.debug(f"extract_video_creation_date for {file_path}")
160
+ logger.debug(f"extract_video_creation_date for {file_path}")
114
161
 
115
162
  if not pl.Path(file_path).is_file():
116
- logging.debug(f"{file_path} not found")
163
+ logger.debug(f"{file_path} not found")
117
164
  return None
118
165
  try:
119
166
  parser = createParser(file_path)
@@ -140,14 +187,14 @@ def extract_date_time_from_file_name(file_path: str) -> int | None:
140
187
 
141
188
  if matches:
142
189
  if pattern == r"\d{4}-\d{2}-\d{2}_\d{6}":
143
- logging.debug(
190
+ logger.debug(
144
191
  f"extract_date_time_from_file_name timestamp from {file_path}: {int(datetime.datetime.strptime(matches[0], '%Y-%m-%d_%H%M%S').timestamp())}"
145
192
  )
146
193
 
147
194
  return int(datetime.datetime.strptime(matches[0], "%Y-%m-%d_%H%M%S").timestamp())
148
195
 
149
196
  if pattern == r"\d{4}-\d{2}-\d{2}_\d{2}:\d{2}:\d{2}":
150
- logging.debug(
197
+ logger.debug(
151
198
  f"extract_date_time_from_file_name timestamp from {file_path}: {int(datetime.datetime.strptime(matches[0], '%Y-%m-%d_%H:%M:%S').timestamp())}"
152
199
  )
153
200
 
@@ -798,9 +845,9 @@ def extract_wav(ffmpeg_bin: str, media_file_path: str, tmp_dir: str) -> str:
798
845
  try:
799
846
  wav = wave.open(media_file_path, "r")
800
847
  wav.close()
801
- logging.debug(f"{media_file_path} is a WAV file. Copying in the temp directory...")
848
+ logger.debug(f"{media_file_path} is a WAV file. Copying in the temp directory...")
802
849
  copyfile(media_file_path, wav_file_path)
803
- logging.debug(f"{media_file_path} copied in {wav_file_path}")
850
+ logger.debug(f"{media_file_path} copied in {wav_file_path}")
804
851
  return str(wav_file_path)
805
852
  except Exception:
806
853
  if wav_file_path.is_file():
@@ -815,7 +862,7 @@ def extract_wav(ffmpeg_bin: str, media_file_path: str, tmp_dir: str) -> str:
815
862
  )
816
863
  out, error = p.communicate()
817
864
  out, error = out.decode("utf-8"), error.decode("utf-8")
818
- logging.debug(f"{out}, {error}")
865
+ logger.debug(f"{out}, {error}")
819
866
 
820
867
  if "does not contain any stream" not in error:
821
868
  if wav_file_path.is_file():
@@ -866,8 +913,8 @@ def seconds_of_day(timestamp: dt.datetime) -> dec:
866
913
  dev: number of seconds since the start of the day
867
914
  """
868
915
 
869
- # logging.debug("function: seconds_of_day")
870
- # logging.debug(f"{timestamp = }")
916
+ # logger.debug("function: seconds_of_day")
917
+ # logger.debug(f"{timestamp = }")
871
918
 
872
919
  t = timestamp.time()
873
920
  return dec(t.hour * 3600 + t.minute * 60 + t.second + t.microsecond / 1000000).quantize(dec("0.001"))
@@ -1200,8 +1247,8 @@ def test_ffmpeg_path(FFmpegPath: str) -> Tuple[bool, str]:
1200
1247
  """
1201
1248
 
1202
1249
  out, error = subprocess.Popen(f'"{FFmpegPath}" -version', stdout=subprocess.PIPE, stderr=subprocess.PIPE, shell=True).communicate()
1203
- logging.debug(f"test ffmpeg path output: {out}")
1204
- logging.debug(f"test ffmpeg path error: {error}")
1250
+ logger.debug(f"test ffmpeg path output: {out}")
1251
+ logger.debug(f"test ffmpeg path error: {error}")
1205
1252
 
1206
1253
  if (b"avconv" in out) or (b"the Libav developers" in error):
1207
1254
  return False, "Please use FFmpeg from https://www.ffmpeg.org in place of FFmpeg from Libav project."
@@ -1223,43 +1270,24 @@ def check_ffmpeg_path() -> Tuple[bool, str]:
1223
1270
  str: if bool True returns ffmpegpath else returns error message
1224
1271
  """
1225
1272
 
1273
+ # search embedded ffmpeg
1226
1274
  if sys.platform.startswith("linux") or sys.platform.startswith("darwin"):
1227
- ffmpeg_path = pl.Path("")
1228
- # search embedded ffmpeg
1229
- if sys.argv[0].endswith("start_boris.py"):
1230
- ffmpeg_path = pl.Path(sys.argv[0]).resolve().parent / "boris" / "misc" / "ffmpeg"
1231
- if sys.argv[0].endswith("__main__.py"):
1232
- ffmpeg_path = pl.Path(sys.argv[0]).resolve().parent / "misc" / "ffmpeg"
1233
-
1234
- if not ffmpeg_path.is_file():
1235
- # search global ffmpeg
1236
- ffmpeg_path = "ffmpeg"
1237
-
1238
- # test ffmpeg
1239
- r, msg = test_ffmpeg_path(str(ffmpeg_path))
1240
- if r:
1241
- return True, str(ffmpeg_path)
1242
- else:
1243
- return False, "FFmpeg is not available"
1275
+ ffmpeg_executable = pl.Path("ffmpeg")
1276
+ elif sys.platform.startswith("win"):
1277
+ ffmpeg_executable = pl.Path("ffmpeg.exe")
1244
1278
 
1245
- if sys.platform.startswith("win"):
1246
- ffmpeg_path = pl.Path("")
1247
- # search embedded ffmpeg
1248
- if sys.argv[0].endswith("start_boris.py"):
1249
- ffmpeg_path = pl.Path(sys.argv[0]).resolve().parent / "boris" / "misc" / "ffmpeg.exe"
1250
- if sys.argv[0].endswith("__main__.py"):
1251
- ffmpeg_path = pl.Path(sys.argv[0]).resolve().parent / "misc" / "ffmpeg.exe"
1252
-
1253
- if not ffmpeg_path.is_file():
1254
- # search global ffmpeg
1255
- ffmpeg_path = "ffmpeg"
1256
-
1257
- # test ffmpeg
1258
- r, msg = test_ffmpeg_path(str(ffmpeg_path))
1259
- if r:
1260
- return True, str(ffmpeg_path)
1261
- else:
1262
- return False, "FFmpeg is not available"
1279
+ ffmpeg_path = pl.Path(__file__).parent / "misc" / ffmpeg_executable
1280
+
1281
+ if not ffmpeg_path.is_file():
1282
+ # search global ffmpeg
1283
+ ffmpeg_path = ffmpeg_executable
1284
+
1285
+ # test ffmpeg
1286
+ r, msg = test_ffmpeg_path(str(ffmpeg_path))
1287
+ if r:
1288
+ return True, str(ffmpeg_path)
1289
+ else:
1290
+ return False, "FFmpeg is not available"
1263
1291
 
1264
1292
 
1265
1293
  def smart_size_format(n: Union[float, int, str, None]) -> str:
@@ -1281,6 +1309,73 @@ def smart_size_format(n: Union[float, int, str, None]) -> str:
1281
1309
  return f"{n / 1_000_000_000:,.1f} Gb"
1282
1310
 
1283
1311
 
1312
+ def get_systeminfo() -> str:
1313
+ """
1314
+ returns info about the system
1315
+ """
1316
+
1317
+ mpv_lib_version_, mpv_lib_file_path, mpv_api_version = mpv_lib_version()
1318
+
1319
+ system_info = (
1320
+ f"BORIS version: {version.__version__}\n"
1321
+ f"OS: {platform.uname().system} {platform.uname().release} {platform.uname().version}\n"
1322
+ f"CPU: {platform.uname().machine} {platform.uname().processor}\n"
1323
+ f"Python {platform.python_version()} ({'64-bit' if sys.maxsize > 2**32 else '32-bit'})\n"
1324
+ f"Qt {qVersion()} - PySide {pyside6_version}\n"
1325
+ f"MPV library version: {mpv_lib_version_}\n"
1326
+ f"MPV API version: {mpv_api_version}\n"
1327
+ f"MPV library file path: {mpv_lib_file_path}\n\n"
1328
+ )
1329
+
1330
+ r, memory = mem_info()
1331
+ if not r:
1332
+ system_info += (
1333
+ f"Memory (RAM) Total: {memory.get('total_memory', 'Not available'):.2f} Mb "
1334
+ f"Free: {memory.get('free_memory', 'Not available'):.2f} Mb\n\n"
1335
+ )
1336
+
1337
+ return system_info
1338
+
1339
+ """
1340
+ # system info
1341
+ systeminfo = ""
1342
+ if sys.platform.startswith("win"):
1343
+ # systeminfo = subprocess.getoutput("systeminfo")
1344
+ systeminfo = subprocess.run("systeminfo /FO csv /NH", capture_output=True, text=True, encoding="mbcs", shell=True).stdout
1345
+
1346
+ import csv
1347
+ from io import StringIO
1348
+
1349
+ # Parse it as CSV
1350
+ f = StringIO(systeminfo)
1351
+ reader = csv.reader(f)
1352
+ parsed_data = list(reader)[0]
1353
+ # Print specific fields by index
1354
+ info_to_show = ""
1355
+ info_to_show += f"Computer Name: {parsed_data[0]}\n"
1356
+ info_to_show += f"OS Name: {parsed_data[1]}\n"
1357
+ info_to_show += f"OS Version: {parsed_data[2]}\n"
1358
+ info_to_show += f"System Manufacturer: {parsed_data[11]}\n"
1359
+ info_to_show += f"System Model: {parsed_data[12]}\n"
1360
+ info_to_show += f"Processor: {parsed_data[14]}\n"
1361
+ info_to_show += f"Locale: {parsed_data[19]}\n"
1362
+ info_to_show += f"Installed Memory: {parsed_data[22]}\n"
1363
+
1364
+ # info about graphic card
1365
+ graphic_info = subprocess.run(
1366
+ "wmic path win32_videocontroller get name", capture_output=True, text=True, encoding="mbcs", shell=True
1367
+ ).stdout
1368
+ info_to_show += graphic_info.replace("\n", "").replace("Name", "Graphic card model")
1369
+
1370
+ systeminfo = info_to_show
1371
+
1372
+ if sys.platform.startswith("linux"):
1373
+ systeminfo = subprocess.getoutput("cat /etc/*rel*; uname -a")
1374
+
1375
+ return systeminfo
1376
+ """
1377
+
1378
+
1284
1379
  def ffprobe_media_analysis(ffmpeg_bin: str, file_name: str) -> dict:
1285
1380
  """
1286
1381
  analyse video parameters with ffprobe (if available)
@@ -1436,8 +1531,8 @@ def accurate_media_analysis(ffmpeg_bin: str, file_name: str) -> dict:
1436
1531
 
1437
1532
  ffprobe_results = ffprobe_media_analysis(ffmpeg_bin, file_name)
1438
1533
 
1439
- logging.debug(f"file_name: {file_name}")
1440
- logging.debug(f"ffprobe_results: {ffprobe_results}")
1534
+ logger.debug(f"file_name: {file_name}")
1535
+ logger.debug(f"ffprobe_results: {ffprobe_results}")
1441
1536
 
1442
1537
  if ("error" not in ffprobe_results) and (ffprobe_results["bitrate"] is not None):
1443
1538
  return ffprobe_results
boris/version.py CHANGED
@@ -20,5 +20,5 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- __version__ = "9.3.5"
24
- __version_date__ = "2025-05-12"
23
+ __version__ = "9.4.1"
24
+ __version_date__ = "2025-05-28"
boris/write_event.py CHANGED
@@ -95,15 +95,24 @@ def write_event(self, event: dict, mem_time: dec) -> int:
95
95
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] in (cfg.MEDIA):
96
96
  if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.MEDIA_CREATION_DATE_AS_OFFSET, False):
97
97
  media_file_name = self.dw_player[0].player.playlist[self.dw_player[0].player.playlist_pos]["filename"]
98
+
99
+ logging.debug(f"{media_file_name=}")
100
+
101
+ media_file_name_posix = pl.Path(media_file_name).as_posix()
102
+
103
+ logging.debug(f"{media_file_name_posix=}")
104
+
98
105
  # add media creation date/time
99
106
 
100
107
  """
101
108
  print(f"{media_file_name=}")
102
109
  print(f"{mem_time=}")
103
- print(f"{dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME][media_file_name])=}")
104
110
  """
111
+ print(f"{self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO]=}")
105
112
 
106
- mem_time += dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME][media_file_name])
113
+ mem_time += dec(
114
+ self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME][media_file_name_posix]
115
+ )
107
116
 
108
117
  # check if time > 2**31 - 1 (2147483647)
109
118
  if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] in (cfg.MEDIA, cfg.VIEWER_MEDIA, cfg.LIVE, cfg.VIEWER_LIVE):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: boris-behav-obs
3
- Version: 9.3.5
3
+ Version: 9.4.1
4
4
  Summary: BORIS - Behavioral Observation Research Interactive Software
5
5
  Author-email: Olivier Friard <olivier.friard@unito.it>
6
6
  License-Expression: GPL-3.0-only
@@ -1,6 +1,6 @@
1
1
  boris/__init__.py,sha256=iAtmVMy22TJpMmxVTMSK_6-wXnCbx1ogvWgfYEcbHzU,773
2
2
  boris/__main__.py,sha256=ANjTbXgXDoz2nB1tCtOIllfIVotCa602iebACX7rXaE,764
3
- boris/about.py,sha256=KEUz6nryrg8FceVyFsf8sMz-xWd2cGwIUfuVydHxqC4,5366
3
+ boris/about.py,sha256=VPa8zeu0bMb1LRXDq8uUSG_7mSbkb2HTk1AtWbzWQwE,5366
4
4
  boris/add_modifier.py,sha256=DWqxkKDBm21QH_kPvhpnltwLtFvPxne0VmZ1SY26hj8,26340
5
5
  boris/add_modifier_ui.py,sha256=Y7TLO5uS6zW7zpjXmjA4V_VIp_bFDNtjOTbJ9Q6m-mQ,11601
6
6
  boris/advanced_event_filtering.py,sha256=VlvU12mL6xYacZOvJAi5uLpHMcmAw5Pvuvmka-PN29c,15469
@@ -11,17 +11,17 @@ boris/boris_cli.py,sha256=n0OiVvZM1gM6E7yKaff9wlgmpAGK4TK052VRi8AabJo,13196
11
11
  boris/cmd_arguments.py,sha256=oWb-FvhKLbKJhATlTHy9muWu8XnnUfOZ-3Fmz2M8Yzc,1848
12
12
  boris/coding_pad.py,sha256=fBKdp7DDyupySJosIYtqNd8s2E-GruzCgVhDFsoVWKE,10986
13
13
  boris/config.py,sha256=PQXm3gJgdCtPOFKLH65sDGw8sk_cdMP53p6eMrlRJl0,17457
14
- boris/config_file.py,sha256=1-2ZmTvKET57rwrLR1dXblt0AxMpGB1LAiHxu-Sy8es,13543
14
+ boris/config_file.py,sha256=bPDDRjtylVJ2ll-FNRjME5iIsIagonZNFns-k9kGW2Q,13545
15
15
  boris/connections.py,sha256=rVI18AuXh8cEnnoCKJk0RMWAaiNOpiaS554Okgk3SBY,19383
16
- boris/converters.py,sha256=c1Jps-URoglY5ILQHz-pCCf6-4DFUHZLtqr_ofsrFg0,11722
16
+ boris/converters.py,sha256=n6gDM9x2hS-ZOoHLruiifuXxnC7ERsUukiFokhHZPoQ,11678
17
17
  boris/converters_ui.py,sha256=uu7LOBV_fKv2DBdOqsqPwjGsjgONr5ODBoscAA-EP48,9900
18
18
  boris/cooccurence.py,sha256=tVERC-V8MWjWHlGEfDuu08iS94qjt4do-38jwI62QaY,10367
19
- boris/core.py,sha256=kmWCzeMv-26bL3a2f_NDnfyes2jLehVwh-Hmlg7c1dI,230405
19
+ boris/core.py,sha256=ZvL5qYRiWU-KWvopKr-9HQhAwMsSC5aV2fMyCyADH8Y,230953
20
20
  boris/core_qrc.py,sha256=J0kom27nrmi4-TCDLJZCZbtUAitiXQ4WEDfErawuxt8,639879
21
21
  boris/core_ui.py,sha256=SeC26uveDCjrCBLsRPuQ6FaapKfON_HIRcQStEDLhl4,76384
22
22
  boris/db_functions.py,sha256=Uw9wWH_Pe-qNzpV1k21YG_jKsoOmfY_iiK_7ARZHGDc,13352
23
23
  boris/dev.py,sha256=9pUElbjl9g17rFUJXX5aVSu55_iIKIuDxNdrB0DI_d0,3671
24
- boris/dialog.py,sha256=ppgaWBLCPfXHLK3_9xpu8h0Nj_m0Ls_4dsZ6lym5wjE,34972
24
+ boris/dialog.py,sha256=LqZ73R9cwiL4qzKyMxeS2G8PKnbVZ0xFvZHw-oSek0M,34039
25
25
  boris/duration_widget.py,sha256=GjZgCAMGOcsNjoPiRImEVe6yMkH2vuNoh44ulpd5nlg,6924
26
26
  boris/edit_event.py,sha256=IMZnopPALNJHgGenl_kMZjqO_Q9GKtyEcyVq5w5VGyU,8001
27
27
  boris/edit_event_ui.py,sha256=qFgt00cejGB6UGC1mFkyZcsIAdvMeYMK0WYjZtJl1T0,9207
@@ -46,14 +46,14 @@ boris/modifiers_coding_map.py,sha256=oT56ZY_PXhEJsMoblEsyNMAPbDpv7ZMOCnvmt7Ibx_Y
46
46
  boris/mpv-1.0.3.py,sha256=EXRtzQqFjOn4wMC6482Ilq3fNQ9N1GRP1VxwLzdeaBY,88077
47
47
  boris/mpv.py,sha256=EfzIHjPbgewG4w3smEtqEUPZoVwYmMQkL4Q8ZyW-a58,76410
48
48
  boris/mpv2.py,sha256=IUI4t4r9GYX7G5OXTjd3RhMMOkDdfal_15buBgksLsk,92152
49
- boris/observation.py,sha256=PcyRjeWnDDniug-cJTr9tqfL0lfMQ02hMk4xY7ZxIvk,57029
50
- boris/observation_operations.py,sha256=6krVy8PrxZ_EUHzUN9FA6Qy17v8QhayVt0uMOMlSDac,106603
49
+ boris/observation.py,sha256=10UkVyY8TDySntIX_-H-IsuFdiF6tEcmC6JQUzD6wYg,57139
50
+ boris/observation_operations.py,sha256=P4CNsUXAM3QBFLu66fBlGQd7huGdiq20nXLn1Si8MbU,106677
51
51
  boris/observation_ui.py,sha256=DAnU94QBNvkLuHT6AxTwqSk_D_n6VUhSl8PexZv_dUk,33309
52
52
  boris/observations_list.py,sha256=NqwECGHtHYmKhSe-qCfqPmJ24SSfzlXvIXS2i3op_zE,10591
53
53
  boris/otx_parser.py,sha256=70QvilzFHXbjAHR88YH0aEXJ3xxheLS5fZGgHFHGpNE,16367
54
54
  boris/param_panel.py,sha256=G0XzNmJIX89-n2OQTDccuY_wWMhr3p7GB4ZorbU6EWc,8786
55
55
  boris/param_panel_ui.py,sha256=4emQDFmuL4_R7bKxosLjdUb-VSPWkDm7suy38F5EKcA,13260
56
- boris/player_dock_widget.py,sha256=cJ2UB6qfdxrk22nLWgOzs5EomCfR7OcznJq67UjF8dg,6186
56
+ boris/player_dock_widget.py,sha256=_qQyj7eet69gjoQnqagmsC41rk4Zp3cQO1lwEm5uZR4,5638
57
57
  boris/plot_data_module.py,sha256=6QbLKfyGp4TYRyHnB9G45y5XrpeXLytcorltEAWfYak,16562
58
58
  boris/plot_events.py,sha256=CF6gnsTeaPG-P1USwh4An2s31NoMJ1roHDImcQrQj3c,24060
59
59
  boris/plot_events_rt.py,sha256=xig__Uea3mQqO5raMBVB3pm3vuQkjAbJpwSS7AwIob8,8327
@@ -77,14 +77,14 @@ boris/synthetic_time_budget.py,sha256=3Eb9onMLmgqCLd50CuxV9L8RV2ESzfaMWvPK_bXUMM
77
77
  boris/time_budget_functions.py,sha256=y5He8crz0xsTxVfz0jATwFFQVnPAIrNHja_0sF6NtRE,52551
78
78
  boris/time_budget_widget.py,sha256=z-tyITBtIz-KH1H2OdMB5a8x9QQLK7Wu96-zkC6NVDA,43213
79
79
  boris/transitions.py,sha256=_aZJfJWv3EBrtmQ7qsdTCayQo6uWU7BXqtQQgflEhr4,12250
80
- boris/utilities.py,sha256=4b3BjO96eR54sPXIYkHwxkUnROGgY5f5vhWKIy8THUo,52646
81
- boris/version.py,sha256=GfkFu2oCmCqcl0AuaKWJ5PwDcop2IbfKoZ_56iiDrIA,787
80
+ boris/utilities.py,sha256=suquxdukTUscCy2M3fbR2-OcFtf_vrx4HcQD3i18XYo,56502
81
+ boris/version.py,sha256=pGQyUdeah1LGsy2_FS1kbsQZu-UFsxxMxKMycE_Ctiw,787
82
82
  boris/video_equalizer.py,sha256=FartoGghFK-T53zklP70rPKYqTuzL8qdvfGlsOF2wwc,5854
83
83
  boris/video_equalizer_ui.py,sha256=1CG3s79eM4JAbaCx3i1ILZXLceb41_gGXlOLNfpBgnw,10142
84
84
  boris/video_operations.py,sha256=mh3iR__Sm2KnV44L_sW2pOo3AgLwlM7wiTnnqQiAVs4,9381
85
85
  boris/view_df.py,sha256=AKScLASX2Uatw7rqPbsnio83eVT4GZYCFhL091eMvlY,3370
86
86
  boris/view_df_ui.py,sha256=CaMeRH_vQ00CTDDFQn73ZZaS-r8BSTWpL-dMCFqzJ_Q,2775
87
- boris/write_event.py,sha256=Fsy_apFl7RLnRsBAwXqACr_URnE_QoAFiPMh0o95ANg,23852
87
+ boris/write_event.py,sha256=RN_cFQZNE2jWbM_BiodL4tsyKT0JPREvUy7xgZnrfaM,24041
88
88
  boris/analysis_plugins/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
89
89
  boris/analysis_plugins/_latency.py,sha256=vLWCPh0cPpAEpdMboCkVIuYO1e3pJlP5tEICe27Sqms,2027
90
90
  boris/analysis_plugins/number_of_occurences.py,sha256=IDyDrdezqvSKT3BlD8QWpSYk8X9nnBBLI80OUnFJ3bY,509
@@ -96,9 +96,9 @@ boris/portion/dict.py,sha256=SyHxc7PfDw2ufNLFQycwJtzmRfL48rDp4UrM2KN7IWc,11282
96
96
  boris/portion/func.py,sha256=3TkQtFKLfsqntwd27HSGHceFhnXHmT-EbNMqktElC5Q,2174
97
97
  boris/portion/interval.py,sha256=bAdUiJjGeUAPgsBAImwNeviiwfQq5odfhFZccAWzOTA,20299
98
98
  boris/portion/io.py,sha256=ppNeRpiLNrocF1yzGeuEUIhYMf2LfsR-cji3d0nmvUs,6371
99
- boris_behav_obs-9.3.5.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
100
- boris_behav_obs-9.3.5.dist-info/METADATA,sha256=X_4eXVCfsqHmHTFqUlJXwAAwNbqVSwEwJTl78vDXC3o,4514
101
- boris_behav_obs-9.3.5.dist-info/WHEEL,sha256=joeZ_q2kZqPjVkNy_YbjGrynLS6bxmBj74YkvIORXVI,109
102
- boris_behav_obs-9.3.5.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
103
- boris_behav_obs-9.3.5.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
104
- boris_behav_obs-9.3.5.dist-info/RECORD,,
99
+ boris_behav_obs-9.4.1.dist-info/licenses/LICENSE.TXT,sha256=WJ7YI-moTFb-uVrFjnzzhGJrnL9P2iqQe8NuED3hutI,35141
100
+ boris_behav_obs-9.4.1.dist-info/METADATA,sha256=LvDrzfRGh4Fok9MBJMCHN_O0-BIMElGDK4Ztsb-ezT8,4514
101
+ boris_behav_obs-9.4.1.dist-info/WHEEL,sha256=JNWh1Fm1UdwIQV075glCn4MVuCRs0sotJIq-J6rbxCU,109
102
+ boris_behav_obs-9.4.1.dist-info/entry_points.txt,sha256=k__8XvFi4vaA4QFvQehCZjYkKmZH34HSAJI2iYCWrMs,52
103
+ boris_behav_obs-9.4.1.dist-info/top_level.txt,sha256=fJSgm62S7WesiwTorGbOO4nNN0yzgZ3klgfGi3Px4qI,6
104
+ boris_behav_obs-9.4.1.dist-info/RECORD,,
@@ -1,5 +1,5 @@
1
1
  Wheel-Version: 1.0
2
- Generator: setuptools (80.4.0)
2
+ Generator: setuptools (80.9.0)
3
3
  Root-Is-Purelib: true
4
4
  Tag: py2-none-any
5
5
  Tag: py3-none-any