boris-behav-obs 9.7.1__py3-none-any.whl → 9.7.12__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 -5
- boris/add_modifier_ui.py +47 -29
- boris/analysis_plugins/_export_to_feral.py +225 -0
- boris/behav_coding_map_creator.py +7 -7
- boris/coding_pad.py +4 -3
- boris/config.py +7 -0
- boris/config_file.py +3 -3
- boris/core.py +98 -60
- boris/ipc_mpv.py +52 -31
- boris/modifier_coding_map_creator.py +6 -6
- boris/observation.py +8 -1
- boris/observation_operations.py +68 -43
- boris/plot_data_module.py +2 -0
- boris/plot_spectrogram_rt.py +3 -0
- boris/plot_waveform_rt.py +4 -1
- boris/plugins.py +60 -23
- boris/preferences.py +9 -0
- boris/preferences_ui.py +48 -28
- boris/project.py +1 -1
- boris/project_functions.py +26 -10
- boris/subjects_pad.py +2 -2
- boris/utilities.py +32 -4
- boris/version.py +2 -2
- boris/video_operations.py +9 -1
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/METADATA +3 -4
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/RECORD +30 -29
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/WHEEL +0 -0
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/entry_points.txt +0 -0
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/licenses/LICENSE.TXT +0 -0
- {boris_behav_obs-9.7.1.dist-info → boris_behav_obs-9.7.12.dist-info}/top_level.txt +0 -0
boris/core.py
CHANGED
|
@@ -41,9 +41,9 @@ from PIL.ImageQt import Image
|
|
|
41
41
|
import subprocess
|
|
42
42
|
import locale
|
|
43
43
|
import tempfile
|
|
44
|
+
import math
|
|
44
45
|
import time
|
|
45
46
|
import urllib.request
|
|
46
|
-
from typing import Union, Tuple
|
|
47
47
|
from decimal import Decimal as dec
|
|
48
48
|
from decimal import ROUND_DOWN
|
|
49
49
|
import gzip
|
|
@@ -54,17 +54,7 @@ import shutil
|
|
|
54
54
|
|
|
55
55
|
matplotlib.use("QtAgg")
|
|
56
56
|
|
|
57
|
-
from PySide6.QtCore import
|
|
58
|
-
Qt,
|
|
59
|
-
QPoint,
|
|
60
|
-
Signal,
|
|
61
|
-
QEvent,
|
|
62
|
-
QDateTime,
|
|
63
|
-
QUrl,
|
|
64
|
-
QAbstractTableModel,
|
|
65
|
-
QElapsedTimer,
|
|
66
|
-
QSettings,
|
|
67
|
-
)
|
|
57
|
+
from PySide6.QtCore import Qt, QPoint, Signal, QEvent, QDateTime, QUrl, QAbstractTableModel, QElapsedTimer, QSettings, QTimer
|
|
68
58
|
from PySide6.QtGui import QIcon, QPixmap, QFont, QKeyEvent, QDesktopServices, QColor, QPainter, QPolygon, QAction
|
|
69
59
|
from PySide6.QtMultimedia import QSoundEffect
|
|
70
60
|
from PySide6.QtWidgets import (
|
|
@@ -242,7 +232,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
242
232
|
current_player: int = 0 # id of the selected (left click) video player
|
|
243
233
|
|
|
244
234
|
mem_media_name: str = "" # record current media name. Use to check if media changed
|
|
245
|
-
mem_playlist_index:
|
|
235
|
+
mem_playlist_index: int | None = None
|
|
246
236
|
saved_state = None
|
|
247
237
|
user_move_slider: bool = False
|
|
248
238
|
observationId: str = "" # current observation id
|
|
@@ -640,7 +630,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
640
630
|
"""
|
|
641
631
|
handle click received from coding pad
|
|
642
632
|
"""
|
|
643
|
-
q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text=behaviorCode)
|
|
633
|
+
q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text=behaviorCode)
|
|
634
|
+
|
|
644
635
|
self.keyPressEvent(q)
|
|
645
636
|
|
|
646
637
|
def close_signal_from_coding_pad(self, geometry, preferences):
|
|
@@ -654,7 +645,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
654
645
|
"""
|
|
655
646
|
handle click received from subjects pad
|
|
656
647
|
"""
|
|
657
|
-
q = QKeyEvent(QEvent.KeyPress, Qt.Key_Enter, Qt.NoModifier, text="#subject#" + subject)
|
|
648
|
+
q = QKeyEvent(QEvent.Type.KeyPress, Qt.Key.Key_Enter, Qt.KeyboardModifier.NoModifier, text="#subject#" + subject)
|
|
658
649
|
self.keyPressEvent(q)
|
|
659
650
|
|
|
660
651
|
def signal_from_subjects_pad(self, event):
|
|
@@ -708,7 +699,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
708
699
|
self.subjects_pad = subjects_pad.SubjectsPad(self.pj, filtered_subjects)
|
|
709
700
|
self.subjects_pad.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
710
701
|
self.subjects_pad.sendEventSignal.connect(self.signal_from_subjects_pad)
|
|
711
|
-
self.subjects_pad.
|
|
702
|
+
self.subjects_pad.click_signal.connect(self.click_signal_from_subjects_pad)
|
|
712
703
|
self.subjects_pad.close_signal.connect(self.close_signal_from_subjects_pad)
|
|
713
704
|
self.subjects_pad.show()
|
|
714
705
|
|
|
@@ -739,7 +730,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
739
730
|
text: str = "Behaviors to show in ethogram list",
|
|
740
731
|
table: str = cfg.ETHOGRAM,
|
|
741
732
|
behavior_type: list = cfg.STATE_EVENT_TYPES,
|
|
742
|
-
) ->
|
|
733
|
+
) -> tuple[bool, list]:
|
|
743
734
|
"""
|
|
744
735
|
allow user to:
|
|
745
736
|
filter behaviors in ethogram widget
|
|
@@ -1510,15 +1501,17 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1510
1501
|
logging.debug("previous media file")
|
|
1511
1502
|
|
|
1512
1503
|
if self.playerType == cfg.MEDIA:
|
|
1513
|
-
if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
|
|
1514
|
-
|
|
1504
|
+
# if len(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][cfg.PLAYER1]) == 1:
|
|
1505
|
+
# self.seek_mediaplayer(dec(0))
|
|
1506
|
+
# return
|
|
1515
1507
|
|
|
1516
1508
|
# check if media not first media
|
|
1517
1509
|
if self.dw_player[0].player.playlist_pos > 0:
|
|
1518
1510
|
self.dw_player[0].player.playlist_prev()
|
|
1519
1511
|
|
|
1520
1512
|
elif self.dw_player[0].player.playlist_count == 1:
|
|
1521
|
-
self.
|
|
1513
|
+
self.seek_mediaplayer(dec(0))
|
|
1514
|
+
# self.statusbar.showMessage("There is only one media file", 5000)
|
|
1522
1515
|
|
|
1523
1516
|
if hasattr(self, "spectro"):
|
|
1524
1517
|
self.spectro.memChunk = -1
|
|
@@ -3682,14 +3675,18 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3682
3675
|
"""
|
|
3683
3676
|
show next frame
|
|
3684
3677
|
"""
|
|
3678
|
+
# frame_step_size = self.config_param.get(cfg.FRAME_STEP_SIZE, cfg.FRAME_STEP_SIZE_DEFAULT_VALUE)
|
|
3679
|
+
|
|
3685
3680
|
if self.playerType == cfg.IMAGES:
|
|
3686
3681
|
if self.image_idx < len(self.images_list) - 1:
|
|
3687
|
-
self.image_idx += 1
|
|
3682
|
+
self.image_idx += 1 # frame_step_size
|
|
3688
3683
|
self.extract_frame(self.dw_player[0])
|
|
3689
3684
|
|
|
3690
3685
|
if self.playerType == cfg.MEDIA:
|
|
3691
3686
|
for dw in self.dw_player:
|
|
3687
|
+
# for _ in range(frame_step_size):
|
|
3692
3688
|
dw.player.frame_step()
|
|
3689
|
+
# time.sleep(0.5)
|
|
3693
3690
|
|
|
3694
3691
|
if self.geometric_measurements_mode:
|
|
3695
3692
|
self.extract_frame(dw)
|
|
@@ -3866,7 +3863,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3866
3863
|
write_event.write_event(self, event, cumulative_time)
|
|
3867
3864
|
# write_event.write_event(self, event, self.getLaps())
|
|
3868
3865
|
|
|
3869
|
-
def get_frame_index(self, player_idx: int = 0) ->
|
|
3866
|
+
def get_frame_index(self, player_idx: int = 0) -> int | str:
|
|
3870
3867
|
"""
|
|
3871
3868
|
returns frame index for player player_idx
|
|
3872
3869
|
"""
|
|
@@ -3947,7 +3944,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
3947
3944
|
slider_position = self.video_slider.value() / (cfg.SLIDER_MAXIMUM - 1)
|
|
3948
3945
|
if self.dw_player[0].player.duration is None:
|
|
3949
3946
|
return
|
|
3950
|
-
|
|
3947
|
+
print(f"{slider_position=}")
|
|
3948
|
+
|
|
3949
|
+
d = self.dw_player[0].player.duration
|
|
3950
|
+
print(f"{d=}")
|
|
3951
|
+
if d is None:
|
|
3952
|
+
return
|
|
3953
|
+
video_position = slider_position * d
|
|
3954
|
+
# video_position = slider_position * self.dw_player[0].player.duration
|
|
3951
3955
|
# self.dw_player[0].player.command("seek", str(video_position), "absolute")
|
|
3952
3956
|
self.dw_player[0].player.seek(video_position, "absolute")
|
|
3953
3957
|
|
|
@@ -4142,7 +4146,22 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4142
4146
|
self.dw_player[n_player].player.playlist_pos = self.dw_player[n_player].player.playlist_count - 1
|
|
4143
4147
|
self.seek_mediaplayer(self.dw_player[n_player].media_durations[-1], player=n_player)
|
|
4144
4148
|
|
|
4145
|
-
def
|
|
4149
|
+
def activate_main_window(self):
|
|
4150
|
+
"""
|
|
4151
|
+
activate main window in order to capture keyboard events
|
|
4152
|
+
called only in IPC mode
|
|
4153
|
+
"""
|
|
4154
|
+
# check if eof reached
|
|
4155
|
+
# print(f"{self.dw_player[0].player.playlist_pos=}")
|
|
4156
|
+
# print(f"{self.dw_player[0].player.playlist_count=}")
|
|
4157
|
+
if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
|
|
4158
|
+
logging.debug("end of playlist reached")
|
|
4159
|
+
if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
|
|
4160
|
+
if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
|
|
4161
|
+
self.pause_video()
|
|
4162
|
+
self.activateWindow()
|
|
4163
|
+
|
|
4164
|
+
def mpv_timer_out(self, value: float | None = None, scroll_slider=True):
|
|
4146
4165
|
"""
|
|
4147
4166
|
print the media current position and total length for MPV player
|
|
4148
4167
|
scroll video slider to video position
|
|
@@ -4152,13 +4171,9 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4152
4171
|
if not self.observationId:
|
|
4153
4172
|
return
|
|
4154
4173
|
|
|
4155
|
-
print("mpv timer out")
|
|
4156
|
-
|
|
4157
4174
|
cumulative_time_pos = self.getLaps()
|
|
4158
|
-
print(f"{cumulative_time_pos=}")
|
|
4159
4175
|
# get frame index
|
|
4160
4176
|
frame_idx = self.get_frame_index()
|
|
4161
|
-
print(f"{frame_idx=}")
|
|
4162
4177
|
# frame_idx = 0
|
|
4163
4178
|
|
|
4164
4179
|
if value is None: # ipc mpv
|
|
@@ -4205,8 +4220,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4205
4220
|
ct = self.getLaps(n_player=n_player)
|
|
4206
4221
|
|
|
4207
4222
|
# sync players 2..8 if time diff >= 1 s
|
|
4208
|
-
if
|
|
4209
|
-
|
|
4223
|
+
if not math.isnan(ct) and not math.isnan(ct0):
|
|
4224
|
+
if (
|
|
4225
|
+
abs(ct0 - (ct + dec(self.pj[cfg.OBSERVATIONS][self.observationId][cfg.MEDIA_INFO][cfg.OFFSET][str(n_player + 1)])))
|
|
4226
|
+
>= 1
|
|
4227
|
+
):
|
|
4228
|
+
self.sync_time(n_player, float(ct0)) # self.seek_mediaplayer(ct0, n_player)
|
|
4210
4229
|
|
|
4211
4230
|
currentTimeOffset = dec(cumulative_time_pos + self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TIME_OFFSET])
|
|
4212
4231
|
|
|
@@ -4237,12 +4256,11 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4237
4256
|
self.show_current_states_in_subjects_table()
|
|
4238
4257
|
|
|
4239
4258
|
# current media name
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
current_playlist_index = self.dw_player[0].player.playlist_pos
|
|
4259
|
+
playlist = self.dw_player[0].player.playlist
|
|
4260
|
+
playlist_pos = self.dw_player[0].player.playlist_pos
|
|
4261
|
+
if playlist is not None and playlist_pos is not None and playlist:
|
|
4262
|
+
current_media_name = Path(playlist[playlist_pos]["filename"]).name
|
|
4263
|
+
current_playlist_index = playlist_pos
|
|
4246
4264
|
else:
|
|
4247
4265
|
current_media_name = ""
|
|
4248
4266
|
current_playlist_index = None
|
|
@@ -4315,15 +4333,14 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4315
4333
|
|
|
4316
4334
|
self.actionPlay.setIcon(QIcon(f":/play_{gui_utilities.theme_mode()}"))
|
|
4317
4335
|
|
|
4318
|
-
print(f"{msg=}")
|
|
4319
|
-
|
|
4320
4336
|
if msg:
|
|
4321
4337
|
self.lb_current_media_time.setText(msg)
|
|
4322
4338
|
|
|
4323
4339
|
# set video scroll bar
|
|
4324
4340
|
|
|
4325
4341
|
if scroll_slider and not self.user_move_slider:
|
|
4326
|
-
|
|
4342
|
+
if current_media_time_pos is not None and current_media_duration is not None:
|
|
4343
|
+
self.video_slider.setValue(round(current_media_time_pos / current_media_duration * (cfg.SLIDER_MAXIMUM - 1)))
|
|
4327
4344
|
|
|
4328
4345
|
def mpv_eof_reached(self):
|
|
4329
4346
|
"""
|
|
@@ -4488,7 +4505,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4488
4505
|
for x in self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
|
|
4489
4506
|
)
|
|
4490
4507
|
|
|
4491
|
-
def choose_behavior(self, obs_key) ->
|
|
4508
|
+
def choose_behavior(self, obs_key) -> None | str:
|
|
4492
4509
|
"""
|
|
4493
4510
|
fill listwidget with all behaviors coded by key
|
|
4494
4511
|
|
|
@@ -4520,7 +4537,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4520
4537
|
else:
|
|
4521
4538
|
return None
|
|
4522
4539
|
|
|
4523
|
-
def choose_subject(self, subject_key) ->
|
|
4540
|
+
def choose_subject(self, subject_key) -> None | str:
|
|
4524
4541
|
"""
|
|
4525
4542
|
fill listwidget with all subjects coded by key
|
|
4526
4543
|
|
|
@@ -4602,13 +4619,15 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4602
4619
|
|
|
4603
4620
|
if self.playerType == cfg.MEDIA:
|
|
4604
4621
|
# cumulative time
|
|
4622
|
+
if self.dw_player[n_player].player.time_pos is None:
|
|
4623
|
+
return dec("NaN")
|
|
4605
4624
|
mem_laps = sum(self.dw_player[n_player].media_durations[0 : self.dw_player[n_player].player.playlist_pos]) + (
|
|
4606
4625
|
0 if self.dw_player[n_player].player.time_pos is None else self.dw_player[n_player].player.time_pos * 1000
|
|
4607
4626
|
)
|
|
4608
4627
|
|
|
4609
4628
|
return dec(str(round(mem_laps / 1000, 3)))
|
|
4610
4629
|
|
|
4611
|
-
def get_obs_time(self, n_player: int = 0) ->
|
|
4630
|
+
def get_obs_time(self, n_player: int = 0) -> tuple[dec, dec | None]:
|
|
4612
4631
|
"""
|
|
4613
4632
|
returns time in current media and cumulative time from begining of observation
|
|
4614
4633
|
do not add time offset
|
|
@@ -4621,7 +4640,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4621
4640
|
"""
|
|
4622
4641
|
|
|
4623
4642
|
if not self.observationId:
|
|
4624
|
-
return dec("0")
|
|
4643
|
+
return dec("0"), dec("0")
|
|
4625
4644
|
|
|
4626
4645
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
|
|
4627
4646
|
if "finished" in self.pb_live_obs.text():
|
|
@@ -4828,7 +4847,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
4828
4847
|
|
|
4829
4848
|
# play / pause with space bar
|
|
4830
4849
|
if self.pj[cfg.OBSERVATIONS][self.observationId][cfg.TYPE] == cfg.LIVE:
|
|
4831
|
-
if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
|
|
4850
|
+
# if ek in (Qt.Key_Space, Qt.Key_Enter, Qt.Key_Return):
|
|
4851
|
+
if ek == Qt.Key_Space:
|
|
4832
4852
|
if self.liveObservationStarted:
|
|
4833
4853
|
if (
|
|
4834
4854
|
dialog.MessageDialog(cfg.programName, "Are you sure to stop the current live observation?", [cfg.YES, cfg.NO])
|
|
@@ -5457,7 +5477,7 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5457
5477
|
check if first player ended
|
|
5458
5478
|
"""
|
|
5459
5479
|
|
|
5460
|
-
|
|
5480
|
+
logging.debug("play_video")
|
|
5461
5481
|
|
|
5462
5482
|
if self.geometric_measurements_mode:
|
|
5463
5483
|
return
|
|
@@ -5465,6 +5485,12 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5465
5485
|
if self.playerType != cfg.MEDIA:
|
|
5466
5486
|
return
|
|
5467
5487
|
|
|
5488
|
+
# check if playlist is ended. If it is ended restart playlist from beginning
|
|
5489
|
+
if self.dw_player[0].player.eof_reached and self.dw_player[0].player.core_idle:
|
|
5490
|
+
if self.dw_player[0].player.playlist_pos is not None and self.dw_player[0].player.playlist_count is not None:
|
|
5491
|
+
if self.dw_player[0].player.playlist_pos == self.dw_player[0].player.playlist_count - 1:
|
|
5492
|
+
self.seek_mediaplayer(dec(0))
|
|
5493
|
+
|
|
5468
5494
|
# check if player 1 is ended
|
|
5469
5495
|
for i, dw in enumerate(self.dw_player):
|
|
5470
5496
|
if (
|
|
@@ -5475,9 +5501,6 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5475
5501
|
|
|
5476
5502
|
self.lb_player_status.clear()
|
|
5477
5503
|
|
|
5478
|
-
# if self.pj[cfg.OBSERVATIONS][self.observationId].get(cfg.VISUALIZE_WAVEFORM, False) \
|
|
5479
|
-
# or self.pj[cfg.OBSERVATIONS][self.observationId].get(VISUALIZE_SPECTROGRAM, False):
|
|
5480
|
-
|
|
5481
5504
|
self.statusbar.showMessage("", 0)
|
|
5482
5505
|
|
|
5483
5506
|
if self.ipc_mpv_timer is not None:
|
|
@@ -5554,16 +5577,19 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5554
5577
|
|
|
5555
5578
|
decrement = self.fast * self.play_rate if self.config_param.get(cfg.ADAPT_FAST_JUMP, cfg.ADAPT_FAST_JUMP_DEFAULT) else self.fast
|
|
5556
5579
|
|
|
5557
|
-
|
|
5558
|
-
|
|
5559
|
-
|
|
5560
|
-
|
|
5561
|
-
|
|
5580
|
+
try:
|
|
5581
|
+
new_time = (
|
|
5582
|
+
sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
|
|
5583
|
+
+ self.dw_player[0].player.playback_time
|
|
5584
|
+
- decrement
|
|
5585
|
+
)
|
|
5586
|
+
except Exception:
|
|
5587
|
+
return
|
|
5562
5588
|
|
|
5563
5589
|
if new_time < decrement:
|
|
5564
5590
|
new_time = 0
|
|
5565
5591
|
|
|
5566
|
-
self.seek_mediaplayer(new_time)
|
|
5592
|
+
self.seek_mediaplayer(dec(new_time))
|
|
5567
5593
|
|
|
5568
5594
|
self.update_visualizations()
|
|
5569
5595
|
|
|
@@ -5577,13 +5603,16 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
5577
5603
|
|
|
5578
5604
|
logging.info(f"Jump forward for {increment} seconds")
|
|
5579
5605
|
|
|
5580
|
-
|
|
5581
|
-
|
|
5582
|
-
|
|
5583
|
-
|
|
5584
|
-
|
|
5606
|
+
try:
|
|
5607
|
+
new_time = (
|
|
5608
|
+
sum(self.dw_player[0].media_durations[0 : self.dw_player[0].player.playlist_pos]) / 1000
|
|
5609
|
+
+ self.dw_player[0].player.playback_time
|
|
5610
|
+
+ increment
|
|
5611
|
+
)
|
|
5612
|
+
except Exception:
|
|
5613
|
+
return
|
|
5585
5614
|
|
|
5586
|
-
self.seek_mediaplayer(new_time)
|
|
5615
|
+
self.seek_mediaplayer(dec(new_time))
|
|
5587
5616
|
|
|
5588
5617
|
self.update_visualizations()
|
|
5589
5618
|
|
|
@@ -5839,6 +5868,15 @@ def main():
|
|
|
5839
5868
|
)
|
|
5840
5869
|
sys.exit()
|
|
5841
5870
|
|
|
5871
|
+
if sys.platform.startswith("darwin"):
|
|
5872
|
+
QMessageBox.warning(
|
|
5873
|
+
None,
|
|
5874
|
+
cfg.programName,
|
|
5875
|
+
("This version of BORIS for macOS is still EXPERIMENTAL and should be used at your own risk."),
|
|
5876
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
5877
|
+
QMessageBox.NoButton,
|
|
5878
|
+
)
|
|
5879
|
+
|
|
5842
5880
|
window.show()
|
|
5843
5881
|
window.raise_() # for overlapping widget (?)
|
|
5844
5882
|
|
boris/ipc_mpv.py
CHANGED
|
@@ -1,8 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS
|
|
3
|
+
Behavioral Observation Research Interactive Software
|
|
4
|
+
Copyright 2012-2025 Olivier Friard
|
|
5
|
+
|
|
6
|
+
This file is part of BORIS.
|
|
7
|
+
|
|
8
|
+
BORIS is free software; you can redistribute it and/or modify
|
|
9
|
+
it under the terms of the GNU General Public License as published by
|
|
10
|
+
the Free Software Foundation; either version 3 of the License, or
|
|
11
|
+
any later version.
|
|
12
|
+
|
|
13
|
+
BORIS is distributed in the hope that it will be useful,
|
|
14
|
+
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
15
|
+
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
16
|
+
GNU General Public License for more details.
|
|
17
|
+
|
|
18
|
+
You should have received a copy of the GNU General Public License
|
|
19
|
+
along with this program; if not see <http://www.gnu.org/licenses/>.
|
|
20
|
+
|
|
21
|
+
"""
|
|
22
|
+
|
|
1
23
|
import socket
|
|
2
24
|
import json
|
|
3
25
|
import subprocess
|
|
4
26
|
|
|
5
|
-
# from PySide6.QtCore import QTimer
|
|
6
27
|
import logging
|
|
7
28
|
import config as cfg
|
|
8
29
|
|
|
@@ -36,11 +57,13 @@ class IPC_MPV:
|
|
|
36
57
|
self.process = subprocess.Popen(
|
|
37
58
|
[
|
|
38
59
|
"mpv",
|
|
60
|
+
"--ontop",
|
|
39
61
|
"--no-border",
|
|
40
62
|
"--osc=no", # no on screen commands
|
|
41
63
|
"--input-ipc-server=" + self.socket_path,
|
|
42
64
|
# "--wid=" + str(int(self.winId())), # Embed in the widget
|
|
43
|
-
"--idle", # Keeps mpv running with no video
|
|
65
|
+
"--idle=yes", # Keeps mpv running with no video
|
|
66
|
+
"--keep-open=always",
|
|
44
67
|
"--input-default-bindings=no",
|
|
45
68
|
"--input-vo-keyboard=no",
|
|
46
69
|
],
|
|
@@ -48,30 +71,6 @@ class IPC_MPV:
|
|
|
48
71
|
stderr=subprocess.PIPE,
|
|
49
72
|
)
|
|
50
73
|
|
|
51
|
-
'''
|
|
52
|
-
def init_socket(self):
|
|
53
|
-
"""
|
|
54
|
-
Initialize the JSON IPC socket.
|
|
55
|
-
"""
|
|
56
|
-
print("init socket")
|
|
57
|
-
self.sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
58
|
-
QTimer.singleShot(5000, self.connect_socket) # Allow time for mpv to initialize
|
|
59
|
-
'''
|
|
60
|
-
|
|
61
|
-
'''
|
|
62
|
-
def connect_socket(self):
|
|
63
|
-
"""
|
|
64
|
-
Connect to the mpv IPC socket.
|
|
65
|
-
"""
|
|
66
|
-
print("connect socket")
|
|
67
|
-
try:
|
|
68
|
-
self.sock.connect(self.socket_path)
|
|
69
|
-
print("Connected to mpv IPC server.")
|
|
70
|
-
except socket.error as e:
|
|
71
|
-
print(f"Failed to connect to mpv IPC server: {e}")
|
|
72
|
-
print("end of connect_socket fucntion")
|
|
73
|
-
'''
|
|
74
|
-
|
|
75
74
|
def send_command(self, command):
|
|
76
75
|
"""
|
|
77
76
|
Send a JSON command to the mpv IPC server.
|
|
@@ -92,9 +91,7 @@ class IPC_MPV:
|
|
|
92
91
|
# Parse the response as JSON
|
|
93
92
|
response_data = json.loads(response.decode("utf-8"))
|
|
94
93
|
if response_data["error"] != "success":
|
|
95
|
-
|
|
96
|
-
print(f"{response_data=}")
|
|
97
|
-
print()
|
|
94
|
+
logging.warning(f"send command: {command} response data: {response_data}")
|
|
98
95
|
# Return the 'data' field which contains the playback position
|
|
99
96
|
return response_data.get("data")
|
|
100
97
|
except FileNotFoundError:
|
|
@@ -128,7 +125,6 @@ class IPC_MPV:
|
|
|
128
125
|
|
|
129
126
|
@pause.setter
|
|
130
127
|
def pause(self, value):
|
|
131
|
-
print(f"set pause to {value}")
|
|
132
128
|
return self.send_command({"command": ["set_property", "pause", value]})
|
|
133
129
|
|
|
134
130
|
@property
|
|
@@ -143,6 +139,14 @@ class IPC_MPV:
|
|
|
143
139
|
def playlist(self):
|
|
144
140
|
return self.send_command({"command": ["get_property", "playlist"]})
|
|
145
141
|
|
|
142
|
+
def playlist_next(self):
|
|
143
|
+
self.send_command({"command": ["playlist-next"]})
|
|
144
|
+
return
|
|
145
|
+
|
|
146
|
+
def playlist_prev(self):
|
|
147
|
+
self.send_command({"command": ["playlist-prev"]})
|
|
148
|
+
return
|
|
149
|
+
|
|
146
150
|
@property
|
|
147
151
|
def playlist_pos(self):
|
|
148
152
|
return self.send_command({"command": ["get_property", "playlist-pos"]})
|
|
@@ -168,7 +172,6 @@ class IPC_MPV:
|
|
|
168
172
|
@property
|
|
169
173
|
def playback_time(self):
|
|
170
174
|
playback_time_ = self.send_command({"command": ["get_property", "playback-time"]})
|
|
171
|
-
print(f"playback_time: {playback_time_}")
|
|
172
175
|
return playback_time_
|
|
173
176
|
|
|
174
177
|
def frame_step(self):
|
|
@@ -292,6 +295,24 @@ class IPC_MPV:
|
|
|
292
295
|
def core_idle(self):
|
|
293
296
|
return self.send_command({"command": ["get_property", "core-idle"]})
|
|
294
297
|
|
|
298
|
+
@property
|
|
299
|
+
def video_pan_x(self):
|
|
300
|
+
return self.send_command({"command": ["get_property", "video-pan-x"]})
|
|
301
|
+
|
|
302
|
+
@video_pan_x.setter
|
|
303
|
+
def video_pan_x(self, value):
|
|
304
|
+
self.send_command({"command": ["set_property", "video-pan-x", value]})
|
|
305
|
+
return
|
|
306
|
+
|
|
307
|
+
@property
|
|
308
|
+
def video_pan_y(self):
|
|
309
|
+
return self.send_command({"command": ["get_property", "video-pan-y"]})
|
|
310
|
+
|
|
311
|
+
@video_pan_y.setter
|
|
312
|
+
def video_pan_y(self, value):
|
|
313
|
+
self.send_command({"command": ["set_property", "video-pan-y", value]})
|
|
314
|
+
return
|
|
315
|
+
|
|
295
316
|
"""
|
|
296
317
|
@property
|
|
297
318
|
def xxx(self):
|
|
@@ -134,7 +134,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
134
134
|
self.saveMapAction.setShortcut("Ctrl+S")
|
|
135
135
|
self.saveMapAction.setStatusTip("Save modifiers map")
|
|
136
136
|
self.saveMapAction.setEnabled(False)
|
|
137
|
-
self.saveMapAction.triggered.connect(self.
|
|
137
|
+
self.saveMapAction.triggered.connect(self.save_map_clicked)
|
|
138
138
|
|
|
139
139
|
self.saveAsMapAction = QAction(QIcon(), "Save modifiers map as", self)
|
|
140
140
|
self.saveAsMapAction.setStatusTip("Save modifiers map as")
|
|
@@ -343,7 +343,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
343
343
|
)
|
|
344
344
|
|
|
345
345
|
if response == cfg.SAVE:
|
|
346
|
-
if not self.
|
|
346
|
+
if not self.save_map_clicked():
|
|
347
347
|
event.ignore()
|
|
348
348
|
|
|
349
349
|
if response == cfg.CANCEL:
|
|
@@ -567,7 +567,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
567
567
|
)
|
|
568
568
|
|
|
569
569
|
if response == "Save":
|
|
570
|
-
if not self.
|
|
570
|
+
if not self.save_map_clicked():
|
|
571
571
|
return
|
|
572
572
|
|
|
573
573
|
if response == "Cancel":
|
|
@@ -606,7 +606,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
606
606
|
)
|
|
607
607
|
|
|
608
608
|
if response == cfg.SAVE:
|
|
609
|
-
if not self.
|
|
609
|
+
if not self.save_map_clicked():
|
|
610
610
|
return
|
|
611
611
|
|
|
612
612
|
if response == cfg.CANCEL:
|
|
@@ -733,7 +733,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
733
733
|
self.fileName += ".boris_map"
|
|
734
734
|
self.saveMap()
|
|
735
735
|
|
|
736
|
-
def
|
|
736
|
+
def save_map_clicked(self):
|
|
737
737
|
if not self.fileName:
|
|
738
738
|
fn = QFileDialog(self).getSaveFileName(
|
|
739
739
|
self,
|
|
@@ -746,7 +746,7 @@ class ModifiersMapCreatorWindow(QMainWindow):
|
|
|
746
746
|
else:
|
|
747
747
|
self.fileName = fn
|
|
748
748
|
|
|
749
|
-
if self.fileName and Path(self.fileName).suffix
|
|
749
|
+
if self.fileName and Path(self.fileName).suffix != ".boris_map":
|
|
750
750
|
self.fileName += ".boris_map"
|
|
751
751
|
|
|
752
752
|
if self.fileName:
|
boris/observation.py
CHANGED
|
@@ -42,6 +42,7 @@ from PySide6.QtWidgets import (
|
|
|
42
42
|
QApplication,
|
|
43
43
|
QMenu,
|
|
44
44
|
QListWidgetItem,
|
|
45
|
+
QHeaderView,
|
|
45
46
|
)
|
|
46
47
|
|
|
47
48
|
from . import config as cfg
|
|
@@ -75,7 +76,10 @@ class AssignConverter(QDialog):
|
|
|
75
76
|
self.cbb[-1].addItems(["None"] + sorted(converters.keys()))
|
|
76
77
|
|
|
77
78
|
if column_idx in col_conv:
|
|
78
|
-
|
|
79
|
+
if col_conv[column_idx] in (["None"] + sorted(converters.keys())):
|
|
80
|
+
self.cbb[-1].setCurrentIndex((["None"] + sorted(converters.keys())).index(col_conv[column_idx]))
|
|
81
|
+
else:
|
|
82
|
+
self.cbb[-1].setCurrentIndex(0)
|
|
79
83
|
else:
|
|
80
84
|
self.cbb[-1].setCurrentIndex(0)
|
|
81
85
|
hbox.addWidget(self.cbb[-1])
|
|
@@ -224,6 +228,9 @@ class Observation(QDialog, Ui_Form):
|
|
|
224
228
|
self.pbCancel.clicked.connect(self.pbCancel_clicked)
|
|
225
229
|
|
|
226
230
|
self.tw_data_files.cellDoubleClicked[int, int].connect(self.tw_data_files_cellDoubleClicked)
|
|
231
|
+
self.tw_data_files.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
|
232
|
+
|
|
233
|
+
self.twVideo1.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeToContents)
|
|
227
234
|
|
|
228
235
|
self.mediaDurations, self.mediaFPS, self.mediaHasVideo, self.mediaHasAudio, self.media_creation_time = {}, {}, {}, {}, {}
|
|
229
236
|
|