boris-behav-obs 9.4.1__tar.gz → 9.5.2__tar.gz
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_behav_obs-9.4.1/boris_behav_obs.egg-info → boris_behav_obs-9.5.2}/PKG-INFO +4 -1
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/analysis_plugins/_latency.py +1 -1
- boris_behav_obs-9.5.2/boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/config.py +10 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/connections.py +2 -1
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/core.py +2 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/core_ui.py +6 -2
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/export_observation.py +2 -6
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/external_processes.py +98 -73
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/import_observations.py +28 -19
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/observation_operations.py +45 -26
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/plot_events.py +1 -1
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/plot_events_rt.py +1 -1
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/plot_spectrogram_rt.py +62 -13
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/plot_waveform_rt.py +1 -1
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/plugins.py +136 -25
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/preferences.py +182 -108
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/preferences_ui.py +216 -32
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/project_functions.py +1 -3
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/utilities.py +21 -14
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/version.py +2 -2
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2/boris_behav_obs.egg-info}/PKG-INFO +4 -1
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris_behav_obs.egg-info/SOURCES.txt +1 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris_behav_obs.egg-info/requires.txt +4 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/pyproject.toml +3 -1
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/LICENSE.TXT +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/MANIFEST.in +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/README.TXT +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/README.md +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/__init__.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/__main__.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/about.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/add_modifier.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/add_modifier_ui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/advanced_event_filtering.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/analysis_plugins/__init__.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/analysis_plugins/number_of_occurences.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/analysis_plugins/number_of_occurences_by_independent_variable.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/analysis_plugins/time_budget.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/behav_coding_map_creator.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/behavior_binary_table.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/behaviors_coding_map.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/boris_cli.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/cmd_arguments.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/coding_pad.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/config_file.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/converters.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/converters_ui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/cooccurence.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/core_qrc.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/db_functions.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/dev.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/dialog.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/duration_widget.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/edit_event.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/edit_event_ui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/event_operations.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/events_cursor.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/events_snapshots.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/exclusion_matrix.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/export_events.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/geometric_measurement.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/gui_utilities.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/image_overlay.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/irr.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/latency.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/measurement_widget.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/media_file.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/menu_options.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/modifier_coding_map_creator.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/modifiers_coding_map.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/mpv-1.0.3.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/mpv.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/mpv2.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/observation.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/observation_ui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/observations_list.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/otx_parser.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/param_panel.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/param_panel_ui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/player_dock_widget.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/plot_data_module.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/portion/__init__.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/portion/const.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/portion/dict.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/portion/func.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/portion/interval.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/portion/io.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/project.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/project_import_export.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/project_ui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/qrc_boris.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/qrc_boris5.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/select_modifiers.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/select_observations.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/select_subj_behav.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/state_events.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/subjects_pad.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/synthetic_time_budget.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/time_budget_functions.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/time_budget_widget.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/transitions.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/video_equalizer.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/video_equalizer_ui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/video_operations.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/view_df.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/view_df_ui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris/write_event.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris_behav_obs.egg-info/dependency_links.txt +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris_behav_obs.egg-info/entry_points.txt +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/boris_behav_obs.egg-info/top_level.txt +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/setup.cfg +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_db_functions.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_export_observation.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_irr.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_observation_gui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_otx_parser.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_preferences_gui.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_project_functions.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_time_budget.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_utilities.py +0 -0
- {boris_behav_obs-9.4.1 → boris_behav_obs-9.5.2}/tests/test_utilities2.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: boris-behav-obs
|
|
3
|
-
Version: 9.
|
|
3
|
+
Version: 9.5.2
|
|
4
4
|
Summary: BORIS - Behavioral Observation Research Interactive Software
|
|
5
5
|
Author-email: Olivier Friard <olivier.friard@unito.it>
|
|
6
6
|
License-Expression: GPL-3.0-only
|
|
@@ -26,10 +26,13 @@ Requires-Dist: tablib[cli,html,ods,pandas,xls,xlsx]>=3
|
|
|
26
26
|
Requires-Dist: pyreadr
|
|
27
27
|
Requires-Dist: pyside6==6.9
|
|
28
28
|
Requires-Dist: hachoir>=3.3.0
|
|
29
|
+
Requires-Dist: scipy>=1.15.3
|
|
29
30
|
Provides-Extra: dev
|
|
30
31
|
Requires-Dist: ruff; extra == "dev"
|
|
31
32
|
Requires-Dist: pytest; extra == "dev"
|
|
32
33
|
Requires-Dist: pytest-cov; extra == "dev"
|
|
34
|
+
Provides-Extra: r
|
|
35
|
+
Requires-Dist: rpy2>=3.6.1; extra == "r"
|
|
33
36
|
Dynamic: license-file
|
|
34
37
|
|
|
35
38
|
BORIS (Behavioral Observation Research Interactive Software)
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""
|
|
2
|
+
BORIS plugin
|
|
3
|
+
|
|
4
|
+
number of occurences of behaviors
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import pandas as pd
|
|
8
|
+
|
|
9
|
+
__version__ = "0.0.1"
|
|
10
|
+
__version_date__ = "2025-06-13"
|
|
11
|
+
__plugin_name__ = "List of dataframe columns"
|
|
12
|
+
__author__ = "Olivier Friard - University of Torino - Italy"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def run(df: pd.DataFrame) -> pd.DataFrame:
|
|
16
|
+
"""
|
|
17
|
+
List the columns present in the dataframe
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
df_results = pd.DataFrame(df.columns, columns=["column name"])
|
|
21
|
+
|
|
22
|
+
return df_results
|
|
@@ -531,6 +531,16 @@ NO_COLOR_CODING_PAD = "#777777"
|
|
|
531
531
|
SPECTROGRAM_COLOR_MAPS = ["viridis", "inferno", "plasma", "magma", "gray", "YlOrRd"]
|
|
532
532
|
SPECTROGRAM_DEFAULT_COLOR_MAP = "viridis"
|
|
533
533
|
SPECTROGRAM_DEFAULT_TIME_INTERVAL = 10
|
|
534
|
+
SPECTROGRAM_WINDOW_TYPE = "SPECTROGRAM_WINDOW_TYPE"
|
|
535
|
+
SPECTROGRAM_DEFAULT_WINDOW_TYPE = "hanning"
|
|
536
|
+
SPECTROGRAM_NFFT = "SPECTROGRAM_NFFT"
|
|
537
|
+
SPECTROGRAM_DEFAULT_NFFT = "1024"
|
|
538
|
+
SPECTROGRAM_NOVERLAP = "SPECTROGRAM_NOVERLAP"
|
|
539
|
+
SPECTROGRAM_DEFAULT_NOVERLAP = 900
|
|
540
|
+
SPECTROGRAM_VMIN = "SPECTROGRAM_VMIN"
|
|
541
|
+
SPECTROGRAM_DEFAULT_VMIN = -100
|
|
542
|
+
SPECTROGRAM_VMAX = "SPECTROGRAM_VMAX"
|
|
543
|
+
SPECTROGRAM_DEFAULT_VMAX = -20
|
|
534
544
|
|
|
535
545
|
# see matplotlib.colors.cnames.keys()
|
|
536
546
|
# https://xkcd.com/color/rgb/
|
|
@@ -193,10 +193,11 @@ def connections(self):
|
|
|
193
193
|
self.actionAdd_image_overlay_on_video.triggered.connect(lambda: image_overlay.add_image_overlay(self))
|
|
194
194
|
self.actionRemove_image_overlay.triggered.connect(lambda: image_overlay.remove_image_overlay(self))
|
|
195
195
|
|
|
196
|
+
self.actionMedia_file_information_2.triggered.connect(lambda: media_file.get_info(self))
|
|
196
197
|
self.actionRecode_resize_video.triggered.connect(lambda: external_processes.ffmpeg_process(self, "reencode_resize"))
|
|
197
198
|
self.actionRotate_video.triggered.connect(lambda: external_processes.ffmpeg_process(self, "rotate"))
|
|
198
199
|
self.actionMerge_media_files.triggered.connect(lambda: external_processes.ffmpeg_process(self, "merge"))
|
|
199
|
-
self.
|
|
200
|
+
self.actionCreate_video_spectrogram.triggered.connect(lambda: external_processes.ffmpeg_process(self, "video_spectrogram"))
|
|
200
201
|
|
|
201
202
|
self.actionCreate_transitions_flow_diagram.triggered.connect(transitions.transitions_dot_script)
|
|
202
203
|
self.actionCreate_transitions_flow_diagram_2.triggered.connect(transitions.transitions_flow_diagram)
|
|
@@ -1034,6 +1034,8 @@ class MainWindow(QMainWindow, Ui_MainWindow):
|
|
|
1034
1034
|
self.spectro.interval = self.spectrogram_time_interval
|
|
1035
1035
|
self.spectro.cursor_color = cfg.REALTIME_PLOT_CURSOR_COLOR
|
|
1036
1036
|
|
|
1037
|
+
self.spectro.config_param = self.config_param
|
|
1038
|
+
|
|
1037
1039
|
# color palette
|
|
1038
1040
|
try:
|
|
1039
1041
|
self.spectro.spectro_color_map = matplotlib.pyplot.get_cmap(self.spectrogram_color_map)
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
################################################################################
|
|
4
4
|
## Form generated from reading UI file 'core.ui'
|
|
5
5
|
##
|
|
6
|
-
## Created by: Qt User Interface Compiler version 6.
|
|
6
|
+
## Created by: Qt User Interface Compiler version 6.9.0
|
|
7
7
|
##
|
|
8
8
|
## WARNING! All changes made in this file will be lost when recompiling UI file!
|
|
9
9
|
################################################################################
|
|
@@ -378,6 +378,8 @@ class Ui_MainWindow(object):
|
|
|
378
378
|
self.actionAdd_frame_indexes.setObjectName(u"actionAdd_frame_indexes")
|
|
379
379
|
self.action_load_plugins = QAction(MainWindow)
|
|
380
380
|
self.action_load_plugins.setObjectName(u"action_load_plugins")
|
|
381
|
+
self.actionCreate_video_spectrogram = QAction(MainWindow)
|
|
382
|
+
self.actionCreate_video_spectrogram.setObjectName(u"actionCreate_video_spectrogram")
|
|
381
383
|
self.centralwidget = QWidget(MainWindow)
|
|
382
384
|
self.centralwidget.setObjectName(u"centralwidget")
|
|
383
385
|
self.horizontalLayout_2 = QHBoxLayout(self.centralwidget)
|
|
@@ -484,7 +486,7 @@ class Ui_MainWindow(object):
|
|
|
484
486
|
MainWindow.setCentralWidget(self.centralwidget)
|
|
485
487
|
self.menubar = QMenuBar(MainWindow)
|
|
486
488
|
self.menubar.setObjectName(u"menubar")
|
|
487
|
-
self.menubar.setGeometry(QRect(0, 0, 1509,
|
|
489
|
+
self.menubar.setGeometry(QRect(0, 0, 1509, 20))
|
|
488
490
|
self.menuHelp = QMenu(self.menubar)
|
|
489
491
|
self.menuHelp.setObjectName(u"menuHelp")
|
|
490
492
|
self.menuFile = QMenu(self.menubar)
|
|
@@ -775,6 +777,7 @@ class Ui_MainWindow(object):
|
|
|
775
777
|
self.menuMedia_file.addAction(self.actionRecode_resize_video)
|
|
776
778
|
self.menuMedia_file.addAction(self.actionRotate_video)
|
|
777
779
|
self.menuMedia_file.addAction(self.actionMerge_media_files)
|
|
780
|
+
self.menuMedia_file.addAction(self.actionCreate_video_spectrogram)
|
|
778
781
|
self.toolBar.addAction(self.action_obs_list)
|
|
779
782
|
self.toolBar.addAction(self.actionPlay)
|
|
780
783
|
self.toolBar.addAction(self.actionReset)
|
|
@@ -1029,6 +1032,7 @@ class Ui_MainWindow(object):
|
|
|
1029
1032
|
self.actionConfigure_tvevents_columns.setText(QCoreApplication.translate("MainWindow", u"Configure columns", None))
|
|
1030
1033
|
self.actionAdd_frame_indexes.setText(QCoreApplication.translate("MainWindow", u"Add frame indexes", None))
|
|
1031
1034
|
self.action_load_plugins.setText(QCoreApplication.translate("MainWindow", u"Load plugins", None))
|
|
1035
|
+
self.actionCreate_video_spectrogram.setText(QCoreApplication.translate("MainWindow", u"Create video spectrogram", None))
|
|
1032
1036
|
self.lbLogoBoris.setText("")
|
|
1033
1037
|
self.lbLogoUnito.setText("")
|
|
1034
1038
|
self.lb_player_status.setText(QCoreApplication.translate("MainWindow", u"lb_player_status", None))
|
|
@@ -691,11 +691,6 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str, force_numbe
|
|
|
691
691
|
# obs description
|
|
692
692
|
obs_description = util.eol2space(observation[cfg.DESCRIPTION])
|
|
693
693
|
|
|
694
|
-
"""
|
|
695
|
-
obs_length = observation_operations.observation_total_length(pj[cfg.OBSERVATIONS][obsId])
|
|
696
|
-
logging.debug(f"obs_length: {obs_length}")
|
|
697
|
-
"""
|
|
698
|
-
|
|
699
694
|
_, _, connector = db_functions.load_aggregated_events_in_db(
|
|
700
695
|
pj, parameters[cfg.SELECTED_SUBJECTS], [obsId], parameters[cfg.SELECTED_BEHAVIORS]
|
|
701
696
|
)
|
|
@@ -798,6 +793,7 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str, force_numbe
|
|
|
798
793
|
if observation[cfg.TYPE] == cfg.MEDIA:
|
|
799
794
|
observation_type = "Media file"
|
|
800
795
|
|
|
796
|
+
# get the media file name of the start of event
|
|
801
797
|
media_file_name = observation_operations.event2media_file_name(observation, row["start"])
|
|
802
798
|
if media_file_name is None:
|
|
803
799
|
media_file_name = "Not found"
|
|
@@ -842,7 +838,7 @@ def export_aggregated_events(pj: dict, parameters: dict, obsId: str, force_numbe
|
|
|
842
838
|
observation["date"].replace("T", " "),
|
|
843
839
|
obs_description,
|
|
844
840
|
observation_type,
|
|
845
|
-
media_file_str,
|
|
841
|
+
media_file_str, # list of media used in observation
|
|
846
842
|
pj[cfg.OBSERVATIONS][obsId][cfg.TIME_OFFSET],
|
|
847
843
|
f"{coding_duration:.3f}" if not coding_duration.is_nan() else cfg.NA,
|
|
848
844
|
media_durations_str,
|
|
@@ -22,7 +22,7 @@ This file is part of BORIS.
|
|
|
22
22
|
|
|
23
23
|
import os
|
|
24
24
|
import tempfile
|
|
25
|
-
|
|
25
|
+
from pathlib import Path
|
|
26
26
|
import logging
|
|
27
27
|
|
|
28
28
|
from PySide6.QtWidgets import QFileDialog, QMessageBox, QInputDialog
|
|
@@ -41,9 +41,9 @@ def ffmpeg_process(self, action: str):
|
|
|
41
41
|
launch ffmpeg process with QProcess
|
|
42
42
|
|
|
43
43
|
Args:
|
|
44
|
-
action (str): "reencode_resize, rotate, merge
|
|
44
|
+
action (str): "reencode_resize, rotate, merge, video_spectrogram
|
|
45
45
|
"""
|
|
46
|
-
if action not in ("reencode_resize", "rotate", "merge"):
|
|
46
|
+
if action not in ("reencode_resize", "rotate", "merge", "video_spectrogram"):
|
|
47
47
|
return
|
|
48
48
|
|
|
49
49
|
def readStdOutput(idx):
|
|
@@ -61,7 +61,7 @@ def ffmpeg_process(self, action: str):
|
|
|
61
61
|
# self.processes_widget.lwi.clear()
|
|
62
62
|
std_out = self.processes[idx - 1][0].readAllStandardOutput().data().decode("utf-8")
|
|
63
63
|
if std_out:
|
|
64
|
-
self.processes_widget.lwi.addItems((f"{
|
|
64
|
+
self.processes_widget.lwi.addItems((f"{Path(self.processes[idx - 1][1][2]).name}: {std_out}",))
|
|
65
65
|
|
|
66
66
|
"""
|
|
67
67
|
std_err = self.processes[idx - 1][0].readAllStandardError().data().decode("utf-8")
|
|
@@ -153,7 +153,7 @@ def ffmpeg_process(self, action: str):
|
|
|
153
153
|
file_list_lst = []
|
|
154
154
|
for file_name in file_names:
|
|
155
155
|
file_list_lst.append(f"file '{file_name}'")
|
|
156
|
-
file_extensions.append(
|
|
156
|
+
file_extensions.append(Path(file_name).suffix)
|
|
157
157
|
if len(set(file_extensions)) > 1:
|
|
158
158
|
QMessageBox.critical(self, cfg.programName, "All media files must have the same format")
|
|
159
159
|
return
|
|
@@ -162,13 +162,13 @@ def ffmpeg_process(self, action: str):
|
|
|
162
162
|
output_file_name, _ = QFileDialog().getSaveFileName(self, "Output file name", "", "*")
|
|
163
163
|
if output_file_name == "":
|
|
164
164
|
return
|
|
165
|
-
if
|
|
165
|
+
if Path(output_file_name).suffix != file_extensions[0]:
|
|
166
166
|
QMessageBox.warning(
|
|
167
167
|
self,
|
|
168
168
|
cfg.programName,
|
|
169
169
|
(
|
|
170
170
|
"The extension of output file must be the same than the extension of input files "
|
|
171
|
-
f"(<b>{file_extensions[0]}</b>).<br>You selected a {
|
|
171
|
+
f"(<b>{file_extensions[0]}</b>).<br>You selected a {Path(output_file_name).suffix} file."
|
|
172
172
|
),
|
|
173
173
|
)
|
|
174
174
|
else:
|
|
@@ -215,93 +215,118 @@ def ffmpeg_process(self, action: str):
|
|
|
215
215
|
self.processes_widget.resize(700, 300)
|
|
216
216
|
|
|
217
217
|
self.processes_widget.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
218
|
+
match action:
|
|
219
|
+
case "reencode_resize":
|
|
220
|
+
self.processes_widget.setWindowTitle("Re-encoding and resizing with FFmpeg")
|
|
221
|
+
case "rotate":
|
|
222
|
+
self.processes_widget.setWindowTitle("Rotating the video with FFmpeg")
|
|
223
|
+
case "merge":
|
|
224
|
+
self.processes_widget.setWindowTitle("Merging media files")
|
|
225
|
+
case "video_spectrogram":
|
|
226
|
+
self.processes_widget.setWindowTitle("Creating a video spectrogram")
|
|
224
227
|
|
|
225
228
|
self.processes_widget.label.setText("This operation can be long. Be patient...\nIn the meanwhile you can continue to use BORIS\n\n")
|
|
226
229
|
self.processes_widget.number_of_files = len(file_names)
|
|
227
230
|
self.processes_widget.show()
|
|
228
231
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
232
|
+
match action:
|
|
233
|
+
case "merge":
|
|
234
|
+
# ffmpeg -f concat -safe 0 -i join_video.txt -c copy output.mp4
|
|
235
|
+
args = ["-hide_banner", "-y", "-f", "concat", "-safe", "0", "-i", file_list, "-c", "copy", output_file_name]
|
|
236
|
+
self.processes.append([QProcess(self), [self.ffmpeg_bin, args, output_file_name]])
|
|
237
|
+
self.processes[-1][0].setProcessChannelMode(QProcess.MergedChannels)
|
|
238
|
+
self.processes[-1][0].readyReadStandardOutput.connect(lambda: readStdOutput(len(self.processes)))
|
|
239
|
+
self.processes[-1][0].readyReadStandardError.connect(lambda: readStdOutput(len(self.processes)))
|
|
240
|
+
self.processes[-1][0].finished.connect(lambda: qprocess_finished(len(self.processes)))
|
|
237
241
|
|
|
238
|
-
|
|
242
|
+
self.processes[-1][0].start(self.processes[-1][1][0], self.processes[-1][1][1])
|
|
239
243
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
244
|
+
case "video_spectrogram":
|
|
245
|
+
# ffmpeg -i video.mp4 -filter_complex showspectrum=mode=combined:color=intensity:slide=1:scale=cbrt -y -acodec copy output.mp4
|
|
246
|
+
for file_name in sorted(file_names, reverse=True):
|
|
247
|
+
output_file_name = str(Path(file_name).with_suffix(f".spectrogram{Path(file_name).suffix}"))
|
|
243
248
|
args = [
|
|
244
249
|
"-hide_banner",
|
|
245
250
|
"-y",
|
|
246
251
|
"-i",
|
|
247
|
-
|
|
248
|
-
"-
|
|
249
|
-
|
|
250
|
-
"-
|
|
251
|
-
|
|
252
|
-
|
|
252
|
+
file_name,
|
|
253
|
+
"-filter_complex",
|
|
254
|
+
"showspectrum=mode=combined:color=intensity:slide=1:scale=cbrt",
|
|
255
|
+
"-acodec",
|
|
256
|
+
"copy",
|
|
257
|
+
output_file_name,
|
|
253
258
|
]
|
|
259
|
+
self.processes.append([QProcess(self), [self.ffmpeg_bin, args, output_file_name]])
|
|
260
|
+
self.processes[-1][0].setProcessChannelMode(QProcess.MergedChannels)
|
|
261
|
+
self.processes[-1][0].readyReadStandardOutput.connect(lambda: readStdOutput(len(self.processes)))
|
|
262
|
+
self.processes[-1][0].readyReadStandardError.connect(lambda: readStdOutput(len(self.processes)))
|
|
263
|
+
self.processes[-1][0].finished.connect(lambda: qprocess_finished(len(self.processes)))
|
|
254
264
|
|
|
255
|
-
|
|
256
|
-
# check bitrate
|
|
257
|
-
r = util.accurate_media_analysis(self.ffmpeg_bin, file_name)
|
|
258
|
-
if "error" not in r and r["bitrate"] is not None:
|
|
259
|
-
current_bitrate = r["bitrate"]
|
|
260
|
-
else:
|
|
261
|
-
current_bitrate = 10_000_000
|
|
262
|
-
|
|
263
|
-
if rotation_idx in (1, 2):
|
|
264
|
-
args = [
|
|
265
|
-
"-hide_banner",
|
|
266
|
-
"-y",
|
|
267
|
-
"-i",
|
|
268
|
-
f"{file_name}",
|
|
269
|
-
"-vf",
|
|
270
|
-
f"transpose={rotation_idx}",
|
|
271
|
-
"-codec:a",
|
|
272
|
-
"copy",
|
|
273
|
-
"-b:v",
|
|
274
|
-
f"{current_bitrate}",
|
|
275
|
-
f"{file_name}.rotated{['', '90', '-90'][rotation_idx]}.avi",
|
|
276
|
-
]
|
|
265
|
+
self.processes[-1][0].start(self.processes[-1][1][0], self.processes[-1][1][1])
|
|
277
266
|
|
|
278
|
-
|
|
267
|
+
case "reencode_resize" | "rotate":
|
|
268
|
+
for file_name in sorted(file_names, reverse=True):
|
|
269
|
+
if action == "reencode_resize":
|
|
279
270
|
args = [
|
|
280
271
|
"-hide_banner",
|
|
281
272
|
"-y",
|
|
282
273
|
"-i",
|
|
283
274
|
f"{file_name}",
|
|
284
275
|
"-vf",
|
|
285
|
-
"
|
|
286
|
-
"-codec:a",
|
|
287
|
-
"copy",
|
|
276
|
+
f"scale={horiz_resol}:-1",
|
|
288
277
|
"-b:v",
|
|
289
|
-
f"{
|
|
290
|
-
f"{file_name}.
|
|
278
|
+
f"{video_quality * 1024 * 1024}",
|
|
279
|
+
f"{file_name}.re-encoded.{horiz_resol}px.{video_quality}Mb.avi",
|
|
291
280
|
]
|
|
292
281
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
282
|
+
if action == "rotate":
|
|
283
|
+
# check bitrate
|
|
284
|
+
r = util.accurate_media_analysis(self.ffmpeg_bin, file_name)
|
|
285
|
+
if "error" not in r and r["bitrate"] is not None:
|
|
286
|
+
current_bitrate = r["bitrate"]
|
|
287
|
+
else:
|
|
288
|
+
current_bitrate = 10_000_000
|
|
289
|
+
|
|
290
|
+
if rotation_idx in (1, 2):
|
|
291
|
+
args = [
|
|
292
|
+
"-hide_banner",
|
|
293
|
+
"-y",
|
|
294
|
+
"-i",
|
|
295
|
+
f"{file_name}",
|
|
296
|
+
"-vf",
|
|
297
|
+
f"transpose={rotation_idx}",
|
|
298
|
+
"-codec:a",
|
|
299
|
+
"copy",
|
|
300
|
+
"-b:v",
|
|
301
|
+
f"{current_bitrate}",
|
|
302
|
+
f"{file_name}.rotated{['', '90', '-90'][rotation_idx]}.avi",
|
|
303
|
+
]
|
|
304
|
+
|
|
305
|
+
if rotation_idx == 3: # 180
|
|
306
|
+
args = [
|
|
307
|
+
"-hide_banner",
|
|
308
|
+
"-y",
|
|
309
|
+
"-i",
|
|
310
|
+
f"{file_name}",
|
|
311
|
+
"-vf",
|
|
312
|
+
"transpose=2,transpose=2",
|
|
313
|
+
"-codec:a",
|
|
314
|
+
"copy",
|
|
315
|
+
"-b:v",
|
|
316
|
+
f"{current_bitrate}",
|
|
317
|
+
f"{file_name}.rotated180.avi",
|
|
318
|
+
]
|
|
319
|
+
|
|
320
|
+
logging.debug("Launch process")
|
|
321
|
+
logging.debug(f"{self.ffmpeg_bin} {' '.join(args)}")
|
|
322
|
+
|
|
323
|
+
self.processes.append([QProcess(self), [self.ffmpeg_bin, args, file_name]])
|
|
324
|
+
|
|
325
|
+
## FFmpeg output the work in progress on stderr
|
|
326
|
+
self.processes[-1][0].setProcessChannelMode(QProcess.MergedChannels)
|
|
327
|
+
self.processes[-1][0].readyReadStandardOutput.connect(lambda: readStdOutput(len(self.processes)))
|
|
328
|
+
# self.processes[-1][0].readyReadStandardError.connect(lambda: readStdOutput(len(self.processes)))
|
|
329
|
+
|
|
330
|
+
self.processes[-1][0].finished.connect(lambda: qprocess_finished(len(self.processes)))
|
|
299
331
|
|
|
300
|
-
|
|
301
|
-
self.processes[-1][0].setProcessChannelMode(QProcess.MergedChannels)
|
|
302
|
-
self.processes[-1][0].readyReadStandardOutput.connect(lambda: readStdOutput(len(self.processes)))
|
|
303
|
-
# self.processes[-1][0].readyReadStandardError.connect(lambda: readStdOutput(len(self.processes)))
|
|
304
|
-
|
|
305
|
-
self.processes[-1][0].finished.connect(lambda: qprocess_finished(len(self.processes)))
|
|
306
|
-
|
|
307
|
-
self.processes[-1][0].start(self.processes[-1][1][0], self.processes[-1][1][1])
|
|
332
|
+
self.processes[-1][0].start(self.processes[-1][1][0], self.processes[-1][1][1])
|
|
@@ -19,10 +19,11 @@ Copyright 2012-2025 Olivier Friard
|
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
import json
|
|
23
22
|
import datetime
|
|
24
|
-
|
|
23
|
+
import gzip
|
|
24
|
+
import json
|
|
25
25
|
import pandas as pd
|
|
26
|
+
from pathlib import Path
|
|
26
27
|
|
|
27
28
|
from PySide6.QtWidgets import (
|
|
28
29
|
QMessageBox,
|
|
@@ -49,8 +50,14 @@ def load_observations_from_boris_project(self, project_file_path: str):
|
|
|
49
50
|
)
|
|
50
51
|
return
|
|
51
52
|
|
|
53
|
+
if project_file_path.endswith(".boris.gz"):
|
|
54
|
+
file_in = gzip.open(project_file_path, mode="rt", encoding="utf-8")
|
|
55
|
+
else:
|
|
56
|
+
file_in = open(project_file_path, "r")
|
|
57
|
+
file_content = file_in.read()
|
|
58
|
+
|
|
52
59
|
try:
|
|
53
|
-
fromProject = json.loads(
|
|
60
|
+
fromProject = json.loads(file_content)
|
|
54
61
|
except Exception:
|
|
55
62
|
QMessageBox.critical(self, cfg.programName, "This project file seems corrupted")
|
|
56
63
|
return
|
|
@@ -84,7 +91,7 @@ def load_observations_from_boris_project(self, project_file_path: str):
|
|
|
84
91
|
if new_behav_set:
|
|
85
92
|
diag_result = dialog.MessageDialog(
|
|
86
93
|
cfg.programName,
|
|
87
|
-
(f"Some coded behaviors in <b>{obs_id}</b> are
|
|
94
|
+
(f"Some coded behaviors in <b>{obs_id}</b> are not defined in the ethogram:<br><b>{', '.join(new_behav_set)}</b>"),
|
|
88
95
|
["Interrupt import", "Skip observation", "Import observation"],
|
|
89
96
|
)
|
|
90
97
|
if diag_result == "Interrupt import":
|
|
@@ -103,7 +110,7 @@ def load_observations_from_boris_project(self, project_file_path: str):
|
|
|
103
110
|
if new_subject_set and new_subject_set != {""}:
|
|
104
111
|
diag_result = dialog.MessageDialog(
|
|
105
112
|
cfg.programName,
|
|
106
|
-
(f"Some coded subjects in <b>{obs_id}</b> are not defined in the project:<br
|
|
113
|
+
(f"Some coded subjects in <b>{obs_id}</b> are not defined in the project:<br><b>{', '.join(new_subject_set)}</b>"),
|
|
107
114
|
["Interrupt import", "Skip observation", "Import observation"],
|
|
108
115
|
)
|
|
109
116
|
|
|
@@ -116,7 +123,7 @@ def load_observations_from_boris_project(self, project_file_path: str):
|
|
|
116
123
|
if obs_id in self.pj[cfg.OBSERVATIONS].keys():
|
|
117
124
|
diag_result = dialog.MessageDialog(
|
|
118
125
|
cfg.programName,
|
|
119
|
-
(f"The observation <b>{obs_id}</b>
|
|
126
|
+
(f"The observation <b>{obs_id}</b>already exists in the current project.<br>"),
|
|
120
127
|
["Interrupt import", "Skip observation", "Rename observation"],
|
|
121
128
|
)
|
|
122
129
|
if diag_result == "Interrupt import":
|
|
@@ -141,18 +148,11 @@ def load_observations_from_spreadsheet(self, project_file_path: str):
|
|
|
141
148
|
import observations from a spreadsheet file
|
|
142
149
|
"""
|
|
143
150
|
|
|
144
|
-
if Path(project_file_path).suffix.
|
|
151
|
+
if Path(project_file_path).suffix.lower() == ".xlsx":
|
|
145
152
|
engine = "openpyxl"
|
|
146
|
-
elif Path(project_file_path).suffix.
|
|
153
|
+
elif Path(project_file_path).suffix.lower() == ".ods":
|
|
147
154
|
engine = "odf"
|
|
148
155
|
else:
|
|
149
|
-
QMessageBox.warning(
|
|
150
|
-
None,
|
|
151
|
-
cfg.programName,
|
|
152
|
-
("The type of file was not recognized. Must be Microsoft-Excel XLSX format or OpenDocument ODS"),
|
|
153
|
-
QMessageBox.Ok | QMessageBox.Default,
|
|
154
|
-
QMessageBox.NoButton,
|
|
155
|
-
)
|
|
156
156
|
return
|
|
157
157
|
|
|
158
158
|
try:
|
|
@@ -167,7 +167,7 @@ def load_observations_from_spreadsheet(self, project_file_path: str):
|
|
|
167
167
|
)
|
|
168
168
|
return
|
|
169
169
|
|
|
170
|
-
expected_labels: list =
|
|
170
|
+
expected_labels: list = ("time", "subject", "code", "modifier", "comment")
|
|
171
171
|
|
|
172
172
|
df.columns = df.columns.str.upper()
|
|
173
173
|
|
|
@@ -210,16 +210,16 @@ def import_observations(self):
|
|
|
210
210
|
"""
|
|
211
211
|
|
|
212
212
|
file_name, _ = QFileDialog().getOpenFileName(
|
|
213
|
-
None, "Choose a file", "", "BORIS project files (*.boris);;Spreadsheet files (*.ods *.xlsx *);;All files (*)"
|
|
213
|
+
None, "Choose a file", "", "BORIS project files (*.boris *.boris.gz);;Spreadsheet files (*.ods *.xlsx *);;All files (*)"
|
|
214
214
|
)
|
|
215
215
|
|
|
216
216
|
if not file_name:
|
|
217
217
|
return
|
|
218
218
|
|
|
219
|
-
if
|
|
219
|
+
if file_name.endswith(".boris") or file_name.endswith(".boris.gz"):
|
|
220
220
|
load_observations_from_boris_project(self, file_name)
|
|
221
221
|
|
|
222
|
-
|
|
222
|
+
elif Path(file_name).suffix.lower() in (".ods", ".xlsx"):
|
|
223
223
|
if not self.observationId:
|
|
224
224
|
QMessageBox.warning(
|
|
225
225
|
None,
|
|
@@ -231,3 +231,12 @@ def import_observations(self):
|
|
|
231
231
|
return
|
|
232
232
|
|
|
233
233
|
load_observations_from_spreadsheet(self, file_name)
|
|
234
|
+
|
|
235
|
+
else:
|
|
236
|
+
QMessageBox.warning(
|
|
237
|
+
None,
|
|
238
|
+
cfg.programName,
|
|
239
|
+
("The type of file was not recognized. Must be a BORIS project or a Microsoft-Excel XLSX format or OpenDocument ODS"),
|
|
240
|
+
QMessageBox.Ok | QMessageBox.Default,
|
|
241
|
+
QMessageBox.NoButton,
|
|
242
|
+
)
|
|
@@ -19,7 +19,7 @@ Copyright 2012-2025 Olivier Friard
|
|
|
19
19
|
MA 02110-1301, USA.
|
|
20
20
|
"""
|
|
21
21
|
|
|
22
|
-
from math import log2
|
|
22
|
+
from math import log2, floor
|
|
23
23
|
import os
|
|
24
24
|
import logging
|
|
25
25
|
import time
|
|
@@ -1055,8 +1055,6 @@ 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
|
-
|
|
1060
1058
|
self.pj[cfg.OBSERVATIONS][new_obs_id][cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME] = observationWindow.media_creation_time
|
|
1061
1059
|
|
|
1062
1060
|
try:
|
|
@@ -2423,34 +2421,55 @@ def event2media_file_name(observation: dict, timestamp: dec) -> Optional[str]:
|
|
|
2423
2421
|
timestamp (dec): time stamp
|
|
2424
2422
|
|
|
2425
2423
|
Returns:
|
|
2426
|
-
str:
|
|
2424
|
+
str: path of media file containing the event
|
|
2427
2425
|
"""
|
|
2426
|
+
if observation.get(cfg.MEDIA_CREATION_DATE_AS_OFFSET, False):
|
|
2427
|
+
# media creation date/time was used for coding
|
|
2428
|
+
video_file_name = None
|
|
2429
|
+
for media_path in observation[cfg.MEDIA_INFO].get(cfg.MEDIA_CREATION_TIME, {}):
|
|
2430
|
+
start_media = observation[cfg.MEDIA_INFO][cfg.MEDIA_CREATION_TIME][media_path]
|
|
2431
|
+
duration = observation[cfg.MEDIA_INFO][cfg.LENGTH][media_path]
|
|
2432
|
+
if start_media <= timestamp <= start_media + duration:
|
|
2433
|
+
video_file_name = media_path
|
|
2434
|
+
break
|
|
2428
2435
|
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
+
else: # no media creation date
|
|
2437
|
+
cumul_media_durations: list = [dec(0)]
|
|
2438
|
+
for media_file in observation[cfg.FILE][cfg.PLAYER1]:
|
|
2439
|
+
try:
|
|
2440
|
+
media_duration = observation[cfg.MEDIA_INFO][cfg.LENGTH][media_file]
|
|
2441
|
+
# cut off media duration to 3 decimal places as that is how fine the player is
|
|
2442
|
+
media_duration = floor(media_duration * 10**3) / dec(10**3)
|
|
2443
|
+
cumul_media_durations.append(floor((cumul_media_durations[-1] + media_duration) * 10**3) / dec(10**3))
|
|
2444
|
+
except KeyError:
|
|
2445
|
+
return None
|
|
2446
|
+
|
|
2447
|
+
"""
|
|
2448
|
+
cumul_media_durations: list = [dec(0)]
|
|
2449
|
+
for media_file in observation[cfg.FILE][cfg.PLAYER1]:
|
|
2450
|
+
try:
|
|
2451
|
+
media_duration = dec(str(observation[cfg.MEDIA_INFO][cfg.LENGTH][media_file]))
|
|
2452
|
+
cumul_media_durations.append(round(cumul_media_durations[-1] + media_duration, 3))
|
|
2453
|
+
except KeyError:
|
|
2454
|
+
return None
|
|
2455
|
+
"""
|
|
2436
2456
|
|
|
2437
|
-
|
|
2457
|
+
cumul_media_durations.remove(dec(0))
|
|
2438
2458
|
|
|
2439
|
-
|
|
2440
|
-
if timestamp == cumul_media_durations[-1]:
|
|
2441
|
-
player_idx = len(observation[cfg.FILE][cfg.PLAYER1]) - 1
|
|
2442
|
-
else:
|
|
2443
|
-
player_idx = -1
|
|
2444
|
-
for idx, value in enumerate(cumul_media_durations):
|
|
2445
|
-
start = 0 if idx == 0 else cumul_media_durations[idx - 1]
|
|
2446
|
-
if start <= timestamp < value:
|
|
2447
|
-
player_idx = idx
|
|
2448
|
-
break
|
|
2459
|
+
logging.debug(f"{cumul_media_durations=}")
|
|
2449
2460
|
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2461
|
+
# test if timestamp is at end of last media
|
|
2462
|
+
if timestamp == cumul_media_durations[-1]:
|
|
2463
|
+
player_idx = len(observation[cfg.FILE][cfg.PLAYER1]) - 1
|
|
2464
|
+
else:
|
|
2465
|
+
player_idx = None
|
|
2466
|
+
for idx, value in enumerate(cumul_media_durations):
|
|
2467
|
+
start = 0 if idx == 0 else cumul_media_durations[idx - 1]
|
|
2468
|
+
if start <= timestamp < value:
|
|
2469
|
+
player_idx = idx
|
|
2470
|
+
break
|
|
2471
|
+
|
|
2472
|
+
video_file_name = observation[cfg.FILE][cfg.PLAYER1][player_idx] if player_idx is not None else None
|
|
2454
2473
|
|
|
2455
2474
|
return video_file_name
|
|
2456
2475
|
|