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.
- boris/__init__.py +1 -1
- boris/__main__.py +1 -1
- boris/about.py +36 -39
- boris/add_modifier.py +122 -109
- boris/add_modifier_ui.py +239 -135
- boris/advanced_event_filtering.py +81 -45
- boris/analysis_plugins/__init__.py +0 -0
- boris/analysis_plugins/_latency.py +59 -0
- boris/analysis_plugins/irr_cohen_kappa.py +109 -0
- boris/analysis_plugins/irr_cohen_kappa_with_modifiers.py +112 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa.py +157 -0
- boris/analysis_plugins/irr_weighted_cohen_kappa_with_modifiers.py +162 -0
- boris/analysis_plugins/list_of_dataframe_columns.py +22 -0
- boris/analysis_plugins/number_of_occurences.py +22 -0
- boris/analysis_plugins/number_of_occurences_by_independent_variable.py +54 -0
- boris/analysis_plugins/time_budget.py +61 -0
- boris/behav_coding_map_creator.py +228 -229
- boris/behavior_binary_table.py +33 -50
- boris/behaviors_coding_map.py +17 -18
- boris/boris_cli.py +6 -25
- boris/cmd_arguments.py +12 -1
- boris/coding_pad.py +42 -49
- boris/config.py +161 -77
- boris/config_file.py +63 -83
- boris/connections.py +112 -57
- boris/converters.py +13 -37
- boris/converters_ui.py +187 -110
- boris/cooccurence.py +250 -0
- boris/core.py +2511 -1824
- boris/core_qrc.py +15895 -10185
- boris/core_ui.py +946 -792
- boris/db_functions.py +21 -41
- boris/dev.py +134 -0
- boris/dialog.py +505 -244
- boris/duration_widget.py +15 -20
- boris/edit_event.py +84 -28
- boris/edit_event_ui.py +214 -78
- boris/event_operations.py +517 -415
- boris/events_cursor.py +25 -17
- boris/events_snapshots.py +36 -82
- boris/exclusion_matrix.py +4 -9
- boris/export_events.py +213 -583
- boris/export_observation.py +98 -611
- boris/external_processes.py +156 -97
- boris/geometric_measurement.py +652 -287
- boris/gui_utilities.py +91 -14
- boris/image_overlay.py +9 -9
- boris/import_observations.py +190 -98
- boris/ipc_mpv.py +325 -0
- boris/irr.py +26 -63
- boris/latency.py +34 -25
- boris/measurement_widget.py +14 -18
- boris/media_file.py +52 -84
- boris/menu_options.py +17 -6
- boris/modifier_coding_map_creator.py +1013 -0
- boris/modifiers_coding_map.py +7 -9
- boris/mpv.py +1 -0
- boris/mpv2.py +732 -705
- boris/observation.py +655 -310
- boris/observation_operations.py +1036 -404
- boris/observation_ui.py +584 -356
- boris/observations_list.py +71 -53
- boris/otx_parser.py +74 -80
- boris/param_panel.py +31 -16
- boris/param_panel_ui.py +254 -138
- boris/player_dock_widget.py +90 -60
- boris/plot_data_module.py +43 -46
- boris/plot_events.py +127 -90
- boris/plot_events_rt.py +17 -31
- boris/plot_spectrogram_rt.py +95 -30
- boris/plot_waveform_rt.py +32 -21
- boris/plugins.py +431 -0
- boris/portion/__init__.py +18 -8
- boris/portion/const.py +35 -18
- boris/portion/dict.py +5 -5
- boris/portion/func.py +2 -2
- boris/portion/interval.py +21 -41
- boris/portion/io.py +41 -32
- boris/preferences.py +306 -83
- boris/preferences_ui.py +685 -228
- boris/project.py +448 -293
- boris/project_functions.py +689 -254
- boris/project_import_export.py +213 -222
- boris/project_ui.py +674 -438
- boris/qrc_boris.py +6 -3
- boris/qrc_boris5.py +6 -3
- boris/select_modifiers.py +74 -48
- boris/select_observations.py +20 -199
- boris/select_subj_behav.py +67 -39
- boris/state_events.py +53 -37
- boris/subjects_pad.py +6 -9
- boris/synthetic_time_budget.py +45 -28
- boris/time_budget_functions.py +171 -171
- boris/time_budget_widget.py +84 -114
- boris/transitions.py +41 -47
- boris/utilities.py +766 -266
- boris/version.py +3 -3
- boris/video_equalizer.py +16 -14
- boris/video_equalizer_ui.py +199 -130
- boris/video_operations.py +125 -28
- boris/view_df.py +104 -0
- boris/view_df_ui.py +75 -0
- boris/write_event.py +538 -0
- boris_behav_obs-9.7.6.dist-info/METADATA +139 -0
- boris_behav_obs-9.7.6.dist-info/RECORD +109 -0
- {boris_behav_obs-8.9.16.dist-info → boris_behav_obs-9.7.6.dist-info}/WHEEL +1 -1
- boris_behav_obs-9.7.6.dist-info/entry_points.txt +2 -0
- boris/README.TXT +0 -22
- boris/add_modifier.ui +0 -323
- boris/boris_ui.py +0 -886
- boris/converters.ui +0 -289
- boris/core.qrc +0 -35
- boris/core.ui +0 -1543
- boris/edit_event.ui +0 -175
- boris/icons/logo_eye.ico +0 -0
- boris/map_creator.py +0 -850
- boris/observation.ui +0 -773
- boris/param_panel.ui +0 -379
- boris/preferences.ui +0 -537
- boris/project.ui +0 -1069
- boris/project_server.py +0 -236
- boris/vlc.py +0 -10343
- boris/vlc_local.py +0 -90
- boris_behav_obs-8.9.16.dist-info/LICENSE.TXT +0 -674
- boris_behav_obs-8.9.16.dist-info/METADATA +0 -129
- boris_behav_obs-8.9.16.dist-info/RECORD +0 -108
- boris_behav_obs-8.9.16.dist-info/entry_points.txt +0 -2
- {boris → boris_behav_obs-9.7.6.dist-info/licenses}/LICENSE.TXT +0 -0
- {boris_behav_obs-8.9.16.dist-info → boris_behav_obs-9.7.6.dist-info}/top_level.txt +0 -0
boris/external_processes.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""
|
|
2
2
|
BORIS
|
|
3
3
|
Behavioral Observation Research Interactive Software
|
|
4
|
-
Copyright 2012-
|
|
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
|
-
|
|
24
23
|
import os
|
|
25
24
|
import tempfile
|
|
26
|
-
|
|
25
|
+
from pathlib import Path
|
|
26
|
+
import logging
|
|
27
27
|
|
|
28
|
-
from
|
|
29
|
-
from
|
|
28
|
+
from PySide6.QtWidgets import QFileDialog, QMessageBox, QInputDialog
|
|
29
|
+
from PySide6.QtCore import (
|
|
30
30
|
Qt,
|
|
31
31
|
QProcess,
|
|
32
32
|
)
|
|
@@ -41,26 +41,36 @@ 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):
|
|
50
|
-
|
|
50
|
+
"""
|
|
51
|
+
read stdout and stderr form qprocess and display them
|
|
52
|
+
"""
|
|
51
53
|
self.processes_widget.label.setText(
|
|
52
54
|
(
|
|
53
|
-
"This operation can be long. Be patient...\n
|
|
55
|
+
"This operation can be long. Be patient...\n"
|
|
56
|
+
"In the meanwhile you can continue to use BORIS\n\n"
|
|
54
57
|
f"Done: {self.processes_widget.number_of_files - len(self.processes)} of {self.processes_widget.number_of_files}"
|
|
55
58
|
)
|
|
56
59
|
)
|
|
57
|
-
|
|
58
|
-
self.processes_widget.lwi.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
60
|
+
|
|
61
|
+
# self.processes_widget.lwi.clear()
|
|
62
|
+
std_out = self.processes[idx - 1][0].readAllStandardOutput().data().decode("utf-8")
|
|
63
|
+
if std_out:
|
|
64
|
+
self.processes_widget.lwi.addItems((f"{Path(self.processes[idx - 1][1][2]).name}: {std_out}",))
|
|
65
|
+
|
|
66
|
+
"""
|
|
67
|
+
std_err = self.processes[idx - 1][0].readAllStandardError().data().decode("utf-8")
|
|
68
|
+
if std_err:
|
|
69
|
+
self.processes_widget.lwi.addItems((f"{pl.Path(self.processes[idx - 1][1][2]).name}: ERROR: {std_err}",))
|
|
70
|
+
self.flag_ffmpeg_error = True
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
self.processes_widget.lwi.scrollToBottom()
|
|
64
74
|
|
|
65
75
|
def qprocess_finished(idx):
|
|
66
76
|
"""
|
|
@@ -69,13 +79,19 @@ def ffmpeg_process(self, action: str):
|
|
|
69
79
|
if self.processes:
|
|
70
80
|
del self.processes[idx - 1]
|
|
71
81
|
if self.processes:
|
|
82
|
+
# start new process
|
|
72
83
|
self.processes[-1][0].start(self.processes[-1][1][0], self.processes[-1][1][1])
|
|
73
84
|
else:
|
|
85
|
+
self.processes_widget.label.setText(
|
|
86
|
+
(f"Done: {self.processes_widget.number_of_files - len(self.processes)} of {self.processes_widget.number_of_files}")
|
|
87
|
+
)
|
|
88
|
+
"""
|
|
74
89
|
self.processes_widget.hide()
|
|
75
90
|
del self.processes_widget
|
|
91
|
+
"""
|
|
76
92
|
|
|
77
93
|
if self.processes:
|
|
78
|
-
QMessageBox.warning(self, cfg.programName, "BORIS is already
|
|
94
|
+
QMessageBox.warning(self, cfg.programName, "BORIS is already running some job.")
|
|
79
95
|
return
|
|
80
96
|
|
|
81
97
|
if action == "merge":
|
|
@@ -84,34 +100,37 @@ def ffmpeg_process(self, action: str):
|
|
|
84
100
|
else:
|
|
85
101
|
msg = f"Select one or more video files to {action.replace('_', ' and ')}"
|
|
86
102
|
file_type = "Video files (*)"
|
|
87
|
-
|
|
88
|
-
fileNames = fn[0] if type(fn) is tuple else fn
|
|
103
|
+
file_names, _ = QFileDialog().getOpenFileNames(self, msg, "", file_type)
|
|
89
104
|
|
|
90
|
-
if not
|
|
105
|
+
if not file_names:
|
|
91
106
|
return
|
|
92
107
|
|
|
93
108
|
if action == "reencode_resize":
|
|
94
|
-
current_bitrate =
|
|
109
|
+
current_bitrate = 10_000_000 # default 10 Mb/s
|
|
95
110
|
current_resolution = 1024
|
|
96
111
|
|
|
97
|
-
r = util.accurate_media_analysis(self.ffmpeg_bin,
|
|
112
|
+
r = util.accurate_media_analysis(self.ffmpeg_bin, file_names[0])
|
|
98
113
|
if "error" in r:
|
|
99
|
-
QMessageBox.warning(self, cfg.programName, f"{
|
|
114
|
+
QMessageBox.warning(self, cfg.programName, f"{file_names[0]}. {r['error']}")
|
|
100
115
|
elif r["has_video"]:
|
|
101
|
-
current_bitrate = r.get("bitrate",
|
|
116
|
+
current_bitrate = r.get("bitrate", None)
|
|
117
|
+
if current_bitrate is None:
|
|
118
|
+
current_bitrate = -1
|
|
119
|
+
else:
|
|
120
|
+
current_bitrate = round(current_bitrate / 1024 / 1024) # Convert to Mb/s
|
|
102
121
|
current_resolution = int(r["resolution"].split("x")[0]) if r["resolution"] is not None else None
|
|
103
122
|
|
|
104
123
|
ib = dialog.Input_dialog(
|
|
105
124
|
"Set the parameters for re-encoding / resizing",
|
|
106
125
|
[
|
|
107
126
|
("sb", "Horizontal resolution (in pixel)", 352, 3840, 100, current_resolution),
|
|
108
|
-
("sb", "Video quality (bitrate)",
|
|
127
|
+
("sb", "Video quality (bitrate Mb/s)", 1, 1000, 1, current_bitrate),
|
|
109
128
|
],
|
|
110
129
|
)
|
|
111
130
|
if not ib.exec_():
|
|
112
131
|
return
|
|
113
132
|
|
|
114
|
-
if len(
|
|
133
|
+
if len(file_names) > 1:
|
|
115
134
|
if (
|
|
116
135
|
dialog.MessageDialog(
|
|
117
136
|
cfg.programName,
|
|
@@ -123,18 +142,18 @@ def ffmpeg_process(self, action: str):
|
|
|
123
142
|
return
|
|
124
143
|
|
|
125
144
|
horiz_resol = ib.elements["Horizontal resolution (in pixel)"].value()
|
|
126
|
-
video_quality = ib.elements["Video quality (bitrate)"].value()
|
|
145
|
+
video_quality = ib.elements["Video quality (bitrate Mb/s)"].value()
|
|
127
146
|
|
|
128
147
|
if action == "merge":
|
|
129
|
-
if len(
|
|
148
|
+
if len(file_names) == 1:
|
|
130
149
|
QMessageBox.critical(self, cfg.programName, "Select more than one file")
|
|
131
150
|
return
|
|
132
151
|
|
|
133
152
|
file_extensions = [] # check extension of 1st media file
|
|
134
153
|
file_list_lst = []
|
|
135
|
-
for file_name in
|
|
154
|
+
for file_name in file_names:
|
|
136
155
|
file_list_lst.append(f"file '{file_name}'")
|
|
137
|
-
file_extensions.append(
|
|
156
|
+
file_extensions.append(Path(file_name).suffix)
|
|
138
157
|
if len(set(file_extensions)) > 1:
|
|
139
158
|
QMessageBox.critical(self, cfg.programName, "All media files must have the same format")
|
|
140
159
|
return
|
|
@@ -143,11 +162,14 @@ def ffmpeg_process(self, action: str):
|
|
|
143
162
|
output_file_name, _ = QFileDialog().getSaveFileName(self, "Output file name", "", "*")
|
|
144
163
|
if output_file_name == "":
|
|
145
164
|
return
|
|
146
|
-
if
|
|
165
|
+
if Path(output_file_name).suffix != file_extensions[0]:
|
|
147
166
|
QMessageBox.warning(
|
|
148
167
|
self,
|
|
149
168
|
cfg.programName,
|
|
150
|
-
|
|
169
|
+
(
|
|
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 {Path(output_file_name).suffix} file."
|
|
172
|
+
),
|
|
151
173
|
)
|
|
152
174
|
else:
|
|
153
175
|
break
|
|
@@ -170,11 +192,13 @@ def ffmpeg_process(self, action: str):
|
|
|
170
192
|
# check if processed files already exist
|
|
171
193
|
if action in ("reencode_resize", "rotate"):
|
|
172
194
|
files_list = []
|
|
173
|
-
for file_name in
|
|
195
|
+
for file_name in file_names:
|
|
174
196
|
if action == "reencode_resize":
|
|
175
|
-
fn = f"{file_name}.re-encoded.{horiz_resol}px.{video_quality}
|
|
197
|
+
fn = f"{file_name}.re-encoded.{horiz_resol}px.{video_quality}Mb.avi"
|
|
198
|
+
|
|
176
199
|
if action == "rotate":
|
|
177
200
|
fn = f"{file_name}.rotated{['', '90', '-90', '180'][rotation_idx]}.avi"
|
|
201
|
+
|
|
178
202
|
if os.path.isfile(fn):
|
|
179
203
|
files_list.append(fn)
|
|
180
204
|
|
|
@@ -188,86 +212,121 @@ def ffmpeg_process(self, action: str):
|
|
|
188
212
|
return
|
|
189
213
|
|
|
190
214
|
self.processes_widget = dialog.Info_widget()
|
|
191
|
-
self.processes_widget.resize(
|
|
192
|
-
self.processes_widget.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
193
|
-
if action == "reencode_resize":
|
|
194
|
-
self.processes_widget.setWindowTitle("Re-encoding and resizing with FFmpeg")
|
|
195
|
-
if action == "rotate":
|
|
196
|
-
self.processes_widget.setWindowTitle("Rotating the video with FFmpeg")
|
|
197
|
-
if action == "merge":
|
|
198
|
-
self.processes_widget.setWindowTitle("Merging media files")
|
|
215
|
+
self.processes_widget.resize(700, 300)
|
|
199
216
|
|
|
200
|
-
self.processes_widget.
|
|
201
|
-
|
|
217
|
+
self.processes_widget.setWindowFlags(Qt.WindowStaysOnTopHint)
|
|
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")
|
|
227
|
+
|
|
228
|
+
self.processes_widget.label.setText("This operation can be long. Be patient...\nIn the meanwhile you can continue to use BORIS\n\n")
|
|
229
|
+
self.processes_widget.number_of_files = len(file_names)
|
|
202
230
|
self.processes_widget.show()
|
|
203
231
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
self.processes[-1][0].start(self.processes[-1][1][0], self.processes[-1][1][1])
|
|
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)))
|
|
214
241
|
|
|
215
|
-
|
|
216
|
-
for file_name in fileNames:
|
|
242
|
+
self.processes[-1][0].start(self.processes[-1][1][0], self.processes[-1][1][1])
|
|
217
243
|
|
|
218
|
-
|
|
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}"))
|
|
219
248
|
args = [
|
|
249
|
+
"-hide_banner",
|
|
220
250
|
"-y",
|
|
221
251
|
"-i",
|
|
222
|
-
|
|
223
|
-
"-
|
|
224
|
-
|
|
225
|
-
"-
|
|
226
|
-
|
|
227
|
-
|
|
252
|
+
file_name,
|
|
253
|
+
"-filter_complex",
|
|
254
|
+
"showspectrum=mode=combined:color=intensity:slide=1:scale=cbrt",
|
|
255
|
+
"-acodec",
|
|
256
|
+
"copy",
|
|
257
|
+
output_file_name,
|
|
228
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)))
|
|
229
264
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
# check bitrate
|
|
233
|
-
r = util.accurate_media_analysis(self.ffmpeg_bin, file_name)
|
|
234
|
-
if "error" not in r and r["bitrate"] != -1:
|
|
235
|
-
video_quality = r["bitrate"]
|
|
236
|
-
else:
|
|
237
|
-
video_quality = 2000
|
|
238
|
-
|
|
239
|
-
if rotation_idx in [1, 2]:
|
|
240
|
-
args = [
|
|
241
|
-
"-y",
|
|
242
|
-
"-i",
|
|
243
|
-
f"{file_name}",
|
|
244
|
-
"-vf",
|
|
245
|
-
f"transpose={rotation_idx}",
|
|
246
|
-
"-codec:a",
|
|
247
|
-
"copy",
|
|
248
|
-
"-b:v",
|
|
249
|
-
f"{video_quality}k",
|
|
250
|
-
f"{file_name}.rotated{['', '90', '-90'][rotation_idx]}.avi",
|
|
251
|
-
]
|
|
265
|
+
self.processes[-1][0].start(self.processes[-1][1][0], self.processes[-1][1][1])
|
|
252
266
|
|
|
253
|
-
|
|
267
|
+
case "reencode_resize" | "rotate":
|
|
268
|
+
for file_name in sorted(file_names, reverse=True):
|
|
269
|
+
if action == "reencode_resize":
|
|
254
270
|
args = [
|
|
271
|
+
"-hide_banner",
|
|
255
272
|
"-y",
|
|
256
273
|
"-i",
|
|
257
274
|
f"{file_name}",
|
|
258
275
|
"-vf",
|
|
259
|
-
"
|
|
260
|
-
"-codec:a",
|
|
261
|
-
"copy",
|
|
276
|
+
f"scale={horiz_resol}:-1",
|
|
262
277
|
"-b:v",
|
|
263
|
-
f"{video_quality}
|
|
264
|
-
f"{file_name}.
|
|
278
|
+
f"{video_quality * 1024 * 1024}",
|
|
279
|
+
f"{file_name}.re-encoded.{horiz_resol}px.{video_quality}Mb.avi",
|
|
265
280
|
]
|
|
266
281
|
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
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)))
|
|
272
331
|
|
|
273
|
-
|
|
332
|
+
self.processes[-1][0].start(self.processes[-1][1][0], self.processes[-1][1][1])
|