boris-behav-obs 8.9.16__py3-none-any.whl → 9.7.6__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.

Potentially problematic release.


This version of boris-behav-obs might be problematic. Click here for more details.

Files changed (129) hide show
  1. boris/__init__.py +1 -1
  2. boris/__main__.py +1 -1
  3. boris/about.py +36 -39
  4. boris/add_modifier.py +122 -109
  5. boris/add_modifier_ui.py +239 -135
  6. boris/advanced_event_filtering.py +81 -45
  7. boris/analysis_plugins/__init__.py +0 -0
  8. boris/analysis_plugins/_latency.py +59 -0
  9. boris/analysis_plugins/irr_cohen_kappa.py +109 -0
  10. boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
  11. boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
  12. boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
  13. boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
  14. boris/analysis_plugins/number_of_occurences.py +22 -0
  15. boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
  16. boris/analysis_plugins/time_budget.py +61 -0
  17. boris/behav_coding_map_creator.py +228 -229
  18. boris/behavior_binary_table.py +33 -50
  19. boris/behaviors_coding_map.py +17 -18
  20. boris/boris_cli.py +6 -25
  21. boris/cmd_arguments.py +12 -1
  22. boris/coding_pad.py +42 -49
  23. boris/config.py +161 -77
  24. boris/config_file.py +63 -83
  25. boris/connections.py +112 -57
  26. boris/converters.py +13 -37
  27. boris/converters_ui.py +187 -110
  28. boris/cooccurence.py +250 -0
  29. boris/core.py +2511 -1824
  30. boris/core_qrc.py +15895 -10185
  31. boris/core_ui.py +946 -792
  32. boris/db_functions.py +21 -41
  33. boris/dev.py +134 -0
  34. boris/dialog.py +505 -244
  35. boris/duration_widget.py +15 -20
  36. boris/edit_event.py +84 -28
  37. boris/edit_event_ui.py +214 -78
  38. boris/event_operations.py +517 -415
  39. boris/events_cursor.py +25 -17
  40. boris/events_snapshots.py +36 -82
  41. boris/exclusion_matrix.py +4 -9
  42. boris/export_events.py +213 -583
  43. boris/export_observation.py +98 -611
  44. boris/external_processes.py +156 -97
  45. boris/geometric_measurement.py +652 -287
  46. boris/gui_utilities.py +91 -14
  47. boris/image_overlay.py +9 -9
  48. boris/import_observations.py +190 -98
  49. boris/ipc_mpv.py +325 -0
  50. boris/irr.py +26 -63
  51. boris/latency.py +34 -25
  52. boris/measurement_widget.py +14 -18
  53. boris/media_file.py +52 -84
  54. boris/menu_options.py +17 -6
  55. boris/modifier_coding_map_creator.py +1013 -0
  56. boris/modifiers_coding_map.py +7 -9
  57. boris/mpv.py +1 -0
  58. boris/mpv2.py +732 -705
  59. boris/observation.py +655 -310
  60. boris/observation_operations.py +1036 -404
  61. boris/observation_ui.py +584 -356
  62. boris/observations_list.py +71 -53
  63. boris/otx_parser.py +74 -80
  64. boris/param_panel.py +31 -16
  65. boris/param_panel_ui.py +254 -138
  66. boris/player_dock_widget.py +90 -60
  67. boris/plot_data_module.py +43 -46
  68. boris/plot_events.py +127 -90
  69. boris/plot_events_rt.py +17 -31
  70. boris/plot_spectrogram_rt.py +95 -30
  71. boris/plot_waveform_rt.py +32 -21
  72. boris/plugins.py +431 -0
  73. boris/portion/__init__.py +18 -8
  74. boris/portion/const.py +35 -18
  75. boris/portion/dict.py +5 -5
  76. boris/portion/func.py +2 -2
  77. boris/portion/interval.py +21 -41
  78. boris/portion/io.py +41 -32
  79. boris/preferences.py +306 -83
  80. boris/preferences_ui.py +685 -228
  81. boris/project.py +448 -293
  82. boris/project_functions.py +689 -254
  83. boris/project_import_export.py +213 -222
  84. boris/project_ui.py +674 -438
  85. boris/qrc_boris.py +6 -3
  86. boris/qrc_boris5.py +6 -3
  87. boris/select_modifiers.py +74 -48
  88. boris/select_observations.py +20 -199
  89. boris/select_subj_behav.py +67 -39
  90. boris/state_events.py +53 -37
  91. boris/subjects_pad.py +6 -9
  92. boris/synthetic_time_budget.py +45 -28
  93. boris/time_budget_functions.py +171 -171
  94. boris/time_budget_widget.py +84 -114
  95. boris/transitions.py +41 -47
  96. boris/utilities.py +766 -266
  97. boris/version.py +3 -3
  98. boris/video_equalizer.py +16 -14
  99. boris/video_equalizer_ui.py +199 -130
  100. boris/video_operations.py +125 -28
  101. boris/view_df.py +104 -0
  102. boris/view_df_ui.py +75 -0
  103. boris/write_event.py +538 -0
  104. boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
  105. boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
  106. {boris_behav_obs-8.9.16.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
  107. boris_behav_obs-9.7.6.dist-info/entry_points.txt +2 -0
  108. boris/README.TXT +0 -22
  109. boris/add_modifier.ui +0 -323
  110. boris/boris_ui.py +0 -886
  111. boris/converters.ui +0 -289
  112. boris/core.qrc +0 -35
  113. boris/core.ui +0 -1543
  114. boris/edit_event.ui +0 -175
  115. boris/icons/logo_eye.ico +0 -0
  116. boris/map_creator.py +0 -850
  117. boris/observation.ui +0 -773
  118. boris/param_panel.ui +0 -379
  119. boris/preferences.ui +0 -537
  120. boris/project.ui +0 -1069
  121. boris/project_server.py +0 -236
  122. boris/vlc.py +0 -10343
  123. boris/vlc_local.py +0 -90
  124. boris_behav_obs-8.9.16.dist-info/LICENSE.TXT +0 -674
  125. boris_behav_obs-8.9.16.dist-info/METADATA +0 -129
  126. boris_behav_obs-8.9.16.dist-info/RECORD +0 -108
  127. boris_behav_obs-8.9.16.dist-info/entry_points.txt +0 -2
  128. {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
  129. {boris_behav_obs-8.9.16.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/latency.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This program is free software; you can redistribute it and/or modify
7
7
  it under the terms of the GNU General Public License as published by
@@ -18,9 +18,7 @@ Copyright 2012-2023 Olivier Friard
18
18
  Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19
19
  MA 02110-1301, USA.
20
20
 
21
- """
22
21
 
23
- """
24
22
  Module for analyzing the latency of behaviors after another behavior(s) (marker)
25
23
 
26
24
  """
@@ -31,7 +29,7 @@ from . import dialog
31
29
  from . import select_observations
32
30
  from . import project_functions, observation_operations
33
31
 
34
- from PyQt5.QtWidgets import QMessageBox
32
+ from PySide6.QtWidgets import QMessageBox
35
33
 
36
34
 
37
35
  def get_latency(self):
@@ -43,10 +41,9 @@ def get_latency(self):
43
41
  None,
44
42
  cfg.programName,
45
43
  (
46
- f"This function is experimental. Please test it and report any bug at <br>"
44
+ "This function is experimental. Please test it and report any bug at <br>"
47
45
  '<a href="https://github.com/olivierfriard/BORIS/issues">'
48
46
  "https://github.com/olivierfriard/BORIS/issues</a><br>"
49
- "or by email (See the About page on the BORIS web site.<br><br>"
50
47
  "Thank you for your collaboration!"
51
48
  ),
52
49
  QMessageBox.Ok | QMessageBox.Default,
@@ -83,10 +80,10 @@ def get_latency(self):
83
80
  )
84
81
  return
85
82
 
86
- parameters = select_subj_behav.choose_obs_subj_behav_category(
83
+ parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
87
84
  self,
88
85
  selected_observations,
89
- flagShowExcludeBehaviorsWoEvents=False,
86
+ show_exclude_non_coded_behaviors=False,
90
87
  window_title="Select the marker behaviors (stimulus)",
91
88
  n_observations=len(selected_observations),
92
89
  )
@@ -102,10 +99,10 @@ def get_latency(self):
102
99
  marker_subjects = parameters[cfg.SELECTED_SUBJECTS]
103
100
  include_marker_modifiers = parameters[cfg.INCLUDE_MODIFIERS]
104
101
 
105
- # print(f"{marker_behaviors=} {marker_subjects=} {include_marker_modifiers=}")
102
+ print(f"{marker_behaviors=} {marker_subjects=} {include_marker_modifiers=}")
106
103
 
107
- parameters = select_subj_behav.choose_obs_subj_behav_category(
108
- self, selected_observations, flagShowExcludeBehaviorsWoEvents=False, window_title="Select the latency behaviors"
104
+ parameters: dict = select_subj_behav.choose_obs_subj_behav_category(
105
+ self, selected_observations, show_exclude_non_coded_behaviors=False, window_title="Select the latency behaviors"
109
106
  )
110
107
  if not parameters[cfg.SELECTED_SUBJECTS] or not parameters[cfg.SELECTED_BEHAVIORS]:
111
108
  return
@@ -113,17 +110,29 @@ def get_latency(self):
113
110
  latency_subjects = parameters[cfg.SELECTED_SUBJECTS]
114
111
  include_latency_modifiers = parameters[cfg.INCLUDE_MODIFIERS]
115
112
 
116
- # print(f"{latency_behaviors=} {latency_subjects=} {include_latency_modifiers=}")
113
+ print(f"{latency_behaviors=} {latency_subjects=} {include_latency_modifiers=}")
117
114
 
118
- results = {}
115
+ results: dict = {}
119
116
  for obs_id in selected_observations:
120
- # print(f"{obs_id=}")
117
+ print(f"{obs_id=}")
121
118
 
122
119
  events_with_status = project_functions.events_start_stop(
123
- self.pj[cfg.ETHOGRAM], self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS]
120
+ self.pj[cfg.ETHOGRAM],
121
+ self.pj[cfg.OBSERVATIONS][obs_id][cfg.EVENTS],
122
+ self.pj[cfg.OBSERVATIONS][obs_id][cfg.TYPE],
124
123
  )
125
124
 
125
+ print(f"{events_with_status=}")
126
+
126
127
  for idx, event in enumerate(events_with_status):
128
+ print(f"{event=}")
129
+
130
+ print(f"{event[cfg.EVENT_STATUS_FIELD_IDX]=}")
131
+
132
+ print(f"{event[cfg.EVENT_BEHAVIOR_FIELD_IDX]=}")
133
+
134
+ print(f"{event[cfg.EVENT_SUBJECT_FIELD_IDX]=}")
135
+
127
136
  if all(
128
137
  (
129
138
  event[cfg.EVENT_STATUS_FIELD_IDX] in (cfg.START, cfg.POINT),
@@ -136,11 +145,13 @@ def get_latency(self):
136
145
  ),
137
146
  )
138
147
  ):
139
-
140
148
  if include_marker_modifiers:
141
149
  marker = event[cfg.EVENT_TIME_FIELD_IDX : cfg.EVENT_MODIFIER_FIELD_IDX + 1]
142
150
  else:
143
151
  marker = event[cfg.EVENT_TIME_FIELD_IDX : cfg.EVENT_BEHAVIOR_FIELD_IDX + 1]
152
+
153
+ print(f"{marker=}")
154
+
144
155
  if marker not in results:
145
156
  results[marker] = {}
146
157
 
@@ -162,20 +173,19 @@ def get_latency(self):
162
173
  ),
163
174
  )
164
175
  ):
165
-
166
- # print(event, event2)
176
+ print(event, event2)
167
177
  if include_latency_modifiers:
168
178
  latency = event2[cfg.EVENT_SUBJECT_FIELD_IDX : cfg.EVENT_MODIFIER_FIELD_IDX + 1]
169
179
  else:
170
180
  latency = event2[cfg.EVENT_SUBJECT_FIELD_IDX : cfg.EVENT_BEHAVIOR_FIELD_IDX + 1]
171
181
 
172
182
  # print(f"{marker=}")
173
- # print(f"{latency=}")
174
- if not latency in results[marker]:
183
+ print(f"{latency=}")
184
+ if latency not in results[marker]:
175
185
  results[marker][latency] = []
176
- results[marker][latency].append(
177
- event2[cfg.EVENT_TIME_FIELD_IDX] - event[cfg.EVENT_TIME_FIELD_IDX]
178
- )
186
+ results[marker][latency].append(event2[cfg.EVENT_TIME_FIELD_IDX] - event[cfg.EVENT_TIME_FIELD_IDX])
187
+
188
+ print(f"{results[marker][latency]=}")
179
189
 
180
190
  # check if new marker
181
191
  if all(
@@ -206,7 +216,6 @@ def get_latency(self):
206
216
  out = ""
207
217
 
208
218
  for marker in sorted(results.keys()):
209
-
210
219
  subject = cfg.NO_FOCAL_SUBJECT if marker[cfg.EVENT_SUBJECT_FIELD_IDX] == "" else marker[1]
211
220
  if include_marker_modifiers:
212
221
  out += f"Marker: <b>{marker[cfg.EVENT_BEHAVIOR_FIELD_IDX]}</b> at {marker[cfg.EVENT_TIME_FIELD_IDX]} s (subject: {subject} - modifiers: {marker[cfg.EVENT_MODIFIER_FIELD_IDX]})<br><br>"
@@ -221,7 +230,7 @@ def get_latency(self):
221
230
 
222
231
  out += "first occurrence: "
223
232
  out += f"{sorted(results[marker][behav])[0]} s<br>"
224
- out += f"all occurrences: "
233
+ out += "all occurrences: "
225
234
 
226
235
  out += ", ".join([f"{x} s" for x in sorted(results[marker][behav])])
227
236
  out += "<br><br>"
@@ -1,8 +1,7 @@
1
- #!/usr/bin/env python3
2
1
  """
3
2
  BORIS
4
3
  Behavioral Observation Research Interactive Software
5
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
6
5
 
7
6
  This file is part of BORIS.
8
7
 
@@ -23,10 +22,10 @@ This file is part of BORIS.
23
22
 
24
23
  import logging
25
24
 
26
- from PyQt5.QtCore import pyqtSignal
25
+ from PySide6.QtCore import Signal
27
26
 
28
- # from PyQt5.QtGui import *
29
- from PyQt5.QtWidgets import (
27
+ # from PySide6.QtGui import *
28
+ from PySide6.QtWidgets import (
30
29
  QApplication,
31
30
  QWidget,
32
31
  QRadioButton,
@@ -40,14 +39,14 @@ from PyQt5.QtWidgets import (
40
39
  QFileDialog,
41
40
  QMessageBox,
42
41
  )
43
- from boris import dialog
44
- from boris.config import YES, NO, CANCEL, programName
42
+ from . import dialog
43
+ from . import config as cfg
45
44
 
46
45
 
47
46
  class wgMeasurement(QWidget):
48
47
  """ """
49
48
 
50
- closeSignal, clearSignal = pyqtSignal(), pyqtSignal()
49
+ closeSignal, clearSignal = Signal(), Signal()
51
50
  flagSaved = True
52
51
  draw_mem = []
53
52
 
@@ -112,7 +111,7 @@ class wgMeasurement(QWidget):
112
111
  self.pbSave = QPushButton("Save results", clicked=self.pbSave_clicked)
113
112
  hbox3.addWidget(self.pbSave)
114
113
 
115
- self.pbClose = QPushButton("Close", clicked=self.pbClose_clicked)
114
+ self.pbClose = QPushButton(cfg.CLOSE, clicked=self.pbClose_clicked)
116
115
  hbox3.addWidget(self.pbClose)
117
116
 
118
117
  vbox.addLayout(hbox3)
@@ -128,13 +127,13 @@ class wgMeasurement(QWidget):
128
127
  def pbClose_clicked(self):
129
128
  if not self.flagSaved:
130
129
  response = dialog.MessageDialog(
131
- programName,
130
+ cfg.programName,
132
131
  "The current results are not saved. Do you want to save results before closing?",
133
- [YES, NO, CANCEL],
132
+ [cfg.YES, cfg.NO, cfg.CANCEL],
134
133
  )
135
- if response == YES:
134
+ if response == cfg.YES:
136
135
  self.pbSave_clicked()
137
- if response == CANCEL:
136
+ if response == cfg.CANCEL:
138
137
  return
139
138
  self.closeSignal.emit()
140
139
 
@@ -143,19 +142,16 @@ class wgMeasurement(QWidget):
143
142
  save results
144
143
  """
145
144
  if self.pte.toPlainText():
146
- fileName, _ = QFileDialog().getSaveFileName(
147
- self, "Save measurement results", "", "Text files (*.txt);;All files (*)"
148
- )
145
+ fileName, _ = QFileDialog().getSaveFileName(self, "Save measurement results", "", "Text files (*.txt);;All files (*)")
149
146
  if fileName:
150
147
  with open(fileName, "w") as f:
151
148
  f.write(self.pte.toPlainText())
152
149
  self.flagSaved = True
153
150
  else:
154
- QMessageBox.information(self, programName, "There are no results to save")
151
+ QMessageBox.information(self, cfg.programName, "There are no results to save")
155
152
 
156
153
 
157
154
  if __name__ == "__main__":
158
-
159
155
  import sys
160
156
 
161
157
  app = QApplication(sys.argv)
boris/media_file.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This file is part of BORIS.
7
7
 
@@ -20,13 +20,13 @@ This file is part of BORIS.
20
20
 
21
21
  """
22
22
 
23
- import logging
23
+ from PySide6.QtWidgets import QFileDialog
24
24
 
25
25
  from . import config as cfg
26
26
  from . import utilities as util
27
27
  from . import dialog
28
28
  from . import project_functions
29
- from PyQt5.QtWidgets import QFileDialog
29
+ from . import utilities as util
30
30
 
31
31
 
32
32
  def get_info(self) -> None:
@@ -34,9 +34,34 @@ def get_info(self) -> None:
34
34
  show info about media file (current media file if an observation is opened)
35
35
  """
36
36
 
37
- if self.observationId and self.playerType == cfg.MEDIA:
37
+ def media_analysis_str(ffmpeg_bin: str, media_full_path: str) -> str:
38
+ r = util.accurate_media_analysis(ffmpeg_bin, media_full_path)
39
+
40
+ if "error" in r:
41
+ ffmpeg_output = f"File path: {media_full_path}<br><br>{r['error']}<br><br>"
42
+ else:
43
+ ffmpeg_output = f"<br><b>{r['analysis_program']} analysis</b><br>"
44
+
45
+ ffmpeg_output += (
46
+ f"File path: <b>{media_full_path}</b><br><br>"
47
+ f"Duration: {r['duration']} seconds ({util.convertTime(self.timeFormat, r['duration'])})<br>"
48
+ f"FPS: {r['fps']}<br>"
49
+ f"Resolution: {r['resolution']} pixels<br>"
50
+ f"Format long name: {r.get('format_long_name', cfg.NA)}<br>"
51
+ f"Creation time: {r.get('creation_time', cfg.NA)}<br>"
52
+ f"Number of frames: {r['frames_number']}<br>"
53
+ f"Bitrate: {util.smart_size_format(r['bitrate'])} <br>"
54
+ f"Has video: {r['has_video']}<br>"
55
+ f"Has audio: {r['has_audio']}<br>"
56
+ f"File size: {util.smart_size_format(r.get('file size', cfg.NA))}<br>"
57
+ f"Video codec: {r.get('video_codec', cfg.NA)}<br>"
58
+ f"Audio codec: {r.get('audio_codec', cfg.NA)}<br>"
59
+ )
38
60
 
39
- tot_output = ""
61
+ return ffmpeg_output
62
+
63
+ if self.observationId and self.playerType == cfg.MEDIA:
64
+ tot_output: str = ""
40
65
 
41
66
  for i, dw in enumerate(self.dw_player):
42
67
  if not (
@@ -45,21 +70,14 @@ def get_info(self) -> None:
45
70
  ):
46
71
  continue
47
72
 
48
- logging.info(f"Video format: {dw.player.video_format}")
49
- logging.info(f"number of media in media list: {dw.player.playlist_count}")
50
- logging.info(f"Current time position: {dw.player.time_pos} duration: {dw.player.duration}")
51
-
52
- logging.info(f"FPS: {dw.player.container_fps}")
53
-
54
- # logging.info("Rate: {}".format(player.mediaplayer.get_rate()))
55
- logging.info(f"Video size: {dw.player.width}x{dw.player.height} ratio: ")
56
-
57
- logging.info(f"Aspect ratio: {round(dw.player.width / dw.player.height, 3)}")
58
- # logging.info("is seekable? {0}".format(player.mediaplayer.is_seekable()))
59
- # logging.info("has_vout? {0}".format(player.mediaplayer.has_vout()))
60
-
61
73
  mpv_output = (
62
74
  "<b>MPV information</b><br>"
75
+ f"Duration: {dw.player.duration} seconds ({util.seconds2time(dw.player.duration)})<br>"
76
+ # "Position: {} %<br>"
77
+ f"FPS: {dw.player.container_fps}<br>"
78
+ # "Rate: {}<br>"
79
+ f"Resolution: {dw.player.width}x{dw.player.height} pixels<br>"
80
+ # "Scale: {}<br>"
63
81
  f"Video format: {dw.player.video_format}<br>"
64
82
  # "State: {}<br>"
65
83
  # "Media Resource Location: {}<br>"
@@ -67,81 +85,31 @@ def get_info(self) -> None:
67
85
  # "Track: {}/{}<br>"
68
86
  f"Number of media in media list: {dw.player.playlist_count}<br>"
69
87
  f"Current time position: {dw.player.time_pos}<br>"
70
- f"duration: {dw.player.duration}<br>"
71
- # "Position: {} %<br>"
72
- f"FPS: {dw.player.container_fps}<br>"
73
- # "Rate: {}<br>"
74
- f"Video size: {dw.player.width}x{dw.player.height}<br>"
75
- # "Scale: {}<br>"
76
88
  f"Aspect ratio: {round(dw.player.width / dw.player.height, 3)}<br>"
77
89
  # "is seekable? {}<br>"
78
90
  # "has_vout? {}<br>"
79
91
  )
80
92
 
81
93
  # FFmpeg/FFprobe analysis
82
-
94
+ ffmpeg_output: str = ""
83
95
  for file_path in self.pj[cfg.OBSERVATIONS][self.observationId][cfg.FILE][str(i + 1)]:
84
96
  media_full_path = project_functions.full_path(file_path, self.projectFileName)
85
- r = util.accurate_media_analysis(self.ffmpeg_bin, media_full_path)
86
- nframes = r["frames_number"]
87
- if "error" in r:
88
- ffmpeg_output += "File path: {filePath}<br><br>{error}<br><br>".format(
89
- filePath=media_full_path, error=r["error"]
90
- )
91
- else:
92
- ffmpeg_output = f"<br><b>{r['analysis_program'] } analysis</b><br>"
93
-
94
- ffmpeg_output += (
95
- f"File path: <b>{media_full_path}</b><br><br>"
96
- f"Duration: {r['duration']} seconds ({util.convertTime(self.timeFormat, r['duration'])})<br>"
97
- f"Resolution: {r['resolution']}<br>"
98
- f"Number of frames: {r['frames_number']}<br>"
99
- f"Bitrate: {r['bitrate']} k<br>"
100
- f"FPS: {r['fps']}<br>"
101
- f"Has video: {r['has_video']}<br>"
102
- f"Has audio: {r['has_audio']}<br>"
103
- f"File size: {r.get('file size', 'NA')}<br>"
104
- f"Video codec: {r.get('video_codec', 'NA')}<br>"
105
- f"Audio codec: {r.get('audio_codec', 'NA')}<br>"
106
- )
107
-
108
- ffmpeg_output += f"Total duration: {sum(self.dw_player[i].media_durations) / 1000} ({util.convertTime(self.timeFormat, sum(self.dw_player[i].media_durations) / 1000)})"
97
+ ffmpeg_output += media_analysis_str(self.ffmpeg_bin, media_full_path)
109
98
 
110
- tot_output += mpv_output + ffmpeg_output + "<br><hr>"
99
+ ffmpeg_output += f"<br>Total duration: {sum(self.dw_player[i].media_durations) / 1000} ({util.convertTime(self.timeFormat, sum(self.dw_player[i].media_durations) / 1000)})"
111
100
 
112
- self.results = dialog.Results_dialog()
113
- self.results.setWindowTitle(cfg.programName + " - Media file information")
114
- self.results.ptText.appendHtml(tot_output)
115
- self.results.show()
101
+ tot_output += mpv_output + ffmpeg_output + "<br><hr>"
116
102
 
117
103
  else: # no open observation
118
-
119
- fn = QFileDialog().getOpenFileName(self, "Select a media file", "", "Media files (*)")
120
- file_path = fn[0] if type(fn) is tuple else fn
121
-
122
- if file_path:
123
- self.results = dialog.Results_dialog()
124
- self.results.setWindowTitle(f"{cfg.programName} - Media file information")
125
-
126
- r = util.accurate_media_analysis(self.ffmpeg_bin, file_path)
127
- if "error" in r:
128
- self.results.ptText.appendHtml(f"File path: {file_path}<br><br>{r['error']}<br><br>")
129
- else:
130
- self.results.ptText.appendHtml(f"<br><b>{r['analysis_program'] } analysis</b><br>")
131
- self.results.ptText.appendHtml(
132
- (
133
- f"File path: <b>{file_path}</b><br><br>"
134
- f"Duration: {r['duration']} seconds ({util.convertTime(self.timeFormat, r['duration'])})<br>"
135
- f"Resolution: {r['resolution']}<br>"
136
- f"Number of frames: {r['frames_number']}<br>"
137
- f"Bitrate: {r['bitrate']} k<br>"
138
- f"FPS: {r['fps']}<br>"
139
- f"Has video: {r['has_video']}<br>"
140
- f"Has audio: {r['has_audio']}<br>"
141
- f"File size: {r.get('file size', 'NA')}<br>"
142
- f"Video codec: {r.get('video_codec', 'NA')}<br>"
143
- f"Audio codec: {r.get('audio_codec', 'NA')}<br>"
144
- )
145
- )
146
-
147
- self.results.show()
104
+ file_paths, _ = QFileDialog().getOpenFileNames(self, "Select a media file", "", "Media files (*)")
105
+ if not file_paths:
106
+ return
107
+
108
+ tot_output: str = ""
109
+ for file_path in file_paths:
110
+ tot_output += media_analysis_str(self.ffmpeg_bin, file_path)
111
+
112
+ self.results = dialog.Results_dialog()
113
+ self.results.setWindowTitle(f"{cfg.programName} - Media file information")
114
+ self.results.ptText.appendHtml(tot_output)
115
+ self.results.show()
boris/menu_options.py CHANGED
@@ -1,7 +1,7 @@
1
1
  """
2
2
  BORIS
3
3
  Behavioral Observation Research Interactive Software
4
- Copyright 2012-2023 Olivier Friard
4
+ Copyright 2012-2025 Olivier Friard
5
5
 
6
6
  This program is free software; you can redistribute it and/or modify
7
7
  it under the terms of the GNU General Public License as published by
@@ -19,9 +19,9 @@ Copyright 2012-2023 Olivier Friard
19
19
  MA 02110-1301, USA.
20
20
  """
21
21
 
22
-
23
22
  import logging
24
23
  from . import config as cfg
24
+ from PySide6.QtCore import QSize
25
25
 
26
26
 
27
27
  def update_windows_title(self):
@@ -57,6 +57,13 @@ def update_menu(self):
57
57
 
58
58
  update_windows_title(self)
59
59
 
60
+ self.toolBar.setIconSize(
61
+ QSize(
62
+ self.config_param.get(cfg.TOOLBAR_ICON_SIZE, cfg.DEFAULT_TOOLBAR_ICON_SIZE_VALUE),
63
+ self.config_param.get(cfg.TOOLBAR_ICON_SIZE, cfg.DEFAULT_TOOLBAR_ICON_SIZE_VALUE),
64
+ )
65
+ )
66
+
60
67
  # enabled if project loaded
61
68
  for action in (
62
69
  self.actionEdit_project,
@@ -65,9 +72,9 @@ def update_menu(self):
65
72
  self.actionExport_project,
66
73
  self.actionCheck_project,
67
74
  self.actionClose_project,
68
- self.actionSend_project,
69
75
  self.actionNew_observation,
70
76
  self.actionRemove_path_from_media_files,
77
+ self.action_create_observations,
71
78
  self.action_obs_list,
72
79
  self.actionExport_observations_list,
73
80
  self.actionExplore_project,
@@ -136,7 +143,10 @@ def update_menu(self):
136
143
  self.actionJumpForward,
137
144
  self.actionJumpBackward,
138
145
  self.actionJumpTo,
146
+ self.action_change_time_offset_of_players,
147
+ self.action_deinterlace,
139
148
  self.actionZoom_level,
149
+ self.actionRotate_current_video,
140
150
  self.actionDisplay_subtitles,
141
151
  self.actionPlay,
142
152
  self.actionReset,
@@ -156,12 +166,12 @@ def update_menu(self):
156
166
  self.menuImage_overlay_on_video_2,
157
167
  self.actionAdd_image_overlay_on_video,
158
168
  self.actionRemove_image_overlay,
169
+ self.actionAdd_frame_indexes,
159
170
  ):
160
-
161
171
  action.setEnabled(self.playerType == cfg.MEDIA)
162
172
 
163
173
  # geometric measurements
164
- self.action_geometric_measurements.setEnabled(observation_is_active and self.geometric_measurements_mode == False)
174
+ self.action_geometric_measurements.setEnabled(observation_is_active and self.geometric_measurements_mode is False)
165
175
  self.actionCoding_pad.setEnabled(observation_is_active)
166
176
  self.actionSubjects_pad.setEnabled(observation_is_active)
167
177
  self.actionBehaviors_coding_map.setEnabled(observation_is_active)
@@ -187,6 +197,7 @@ def update_menu(self):
187
197
  self.action_behavior_binary_table,
188
198
  self.action_advanced_event_filtering,
189
199
  self.action_latency,
200
+ self.action_cooccurence,
190
201
  self.menuPlot_events,
191
202
  self.menuInter_rater_reliability,
192
203
  self.menuSimilarities,
@@ -196,7 +207,7 @@ def update_menu(self):
196
207
  w.setEnabled(project_contains_obs)
197
208
 
198
209
  # statusbar labels
199
- for w in [self.lbTimeOffset, self.lbSpeed, self.lb_obs_time_interval]:
210
+ for w in (self.lbTimeOffset, self.lb_obs_time_interval):
200
211
  w.setVisible(self.playerType == cfg.MEDIA)
201
212
 
202
213
  logging.debug("function: menu_options finished")