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 +5 -4
- boris/config_file.py +1 -1
- boris/converters.py +1 -3
- boris/core.py +126 -116
- boris/dialog.py +4 -25
- boris/observation.py +8 -4
- boris/observation_operations.py +2 -0
- boris/player_dock_widget.py +0 -24
- boris/utilities.py +170 -75
- boris/version.py +2 -2
- boris/write_event.py +11 -2
- {boris_behav_obs-9.3.5.dist-info → boris_behav_obs-9.4.1.dist-info}/METADATA +1 -1
- {boris_behav_obs-9.3.5.dist-info → boris_behav_obs-9.4.1.dist-info}/RECORD +17 -17
- {boris_behav_obs-9.3.5.dist-info → boris_behav_obs-9.4.1.dist-info}/WHEEL +1 -1
- {boris_behav_obs-9.3.5.dist-info → boris_behav_obs-9.4.1.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.3.5.dist-info → boris_behav_obs-9.4.1.dist-info}/licenses/LICENSE.TXT +0 -0
- {boris_behav_obs-9.3.5.dist-info → boris_behav_obs-9.4.1.dist-info}/top_level.txt +0 -0
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
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:
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
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}",
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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 =
|
|
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
|
|
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>{
|
|
1731
|
-
msg += f"<br>File 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(
|
|
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
|
|
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 =
|
|
1866
|
-
image_file_path = str(
|
|
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
|
|
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(
|
|
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
|
|
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 =
|
|
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 =
|
|
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(
|
|
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(
|
|
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 (
|
|
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 {
|
|
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(
|
|
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
|
|
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(
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 =
|
|
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
|
-
|
|
5736
|
-
|
|
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 =
|
|
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
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
-
|
|
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
|
|
boris/observation_operations.py
CHANGED
|
@@ -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:
|
boris/player_dock_widget.py
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
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
|
-
|
|
160
|
+
logger.debug(f"extract_video_creation_date for {file_path}")
|
|
114
161
|
|
|
115
162
|
if not pl.Path(file_path).is_file():
|
|
116
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
#
|
|
870
|
-
#
|
|
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
|
-
|
|
1204
|
-
|
|
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
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
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
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
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
|
-
|
|
1440
|
-
|
|
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
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(
|
|
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
|
boris/__init__.py,sha256=iAtmVMy22TJpMmxVTMSK_6-wXnCbx1ogvWgfYEcbHzU,773
|
|
2
2
|
boris/__main__.py,sha256=ANjTbXgXDoz2nB1tCtOIllfIVotCa602iebACX7rXaE,764
|
|
3
|
-
boris/about.py,sha256=
|
|
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=
|
|
14
|
+
boris/config_file.py,sha256=bPDDRjtylVJ2ll-FNRjME5iIsIagonZNFns-k9kGW2Q,13545
|
|
15
15
|
boris/connections.py,sha256=rVI18AuXh8cEnnoCKJk0RMWAaiNOpiaS554Okgk3SBY,19383
|
|
16
|
-
boris/converters.py,sha256=
|
|
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=
|
|
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=
|
|
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=
|
|
50
|
-
boris/observation_operations.py,sha256=
|
|
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=
|
|
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=
|
|
81
|
-
boris/version.py,sha256=
|
|
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=
|
|
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.
|
|
100
|
-
boris_behav_obs-9.
|
|
101
|
-
boris_behav_obs-9.
|
|
102
|
-
boris_behav_obs-9.
|
|
103
|
-
boris_behav_obs-9.
|
|
104
|
-
boris_behav_obs-9.
|
|
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,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|