-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathblackbox2gpmf.py
200 lines (159 loc) · 6.38 KB
/
blackbox2gpmf.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
import subprocess
import re
import csv
import numpy as np
import pandas as pd
from scipy import signal
from shutil import copyfile
from tkinter import filedialog, messagebox
import matplotlib
from matplotlib.widgets import Button, Slider
import matplotlib.pyplot as plt
matplotlib.use('TkAgg')
fig, ax = plt.subplots()
plt.subplots_adjust(bottom=0.25)
fig.canvas.set_window_title('blackbox2gpmf by jaromeyer')
gp_gyro, gp_offset, bbl_gyro, bbl_frame = [[]] * 4
bbl_plot, gp_file, gp_offsets = [None] * 3
def load_gp(event):
global gp_gyro, gp_offsets, gp_file, bbl_plot
gp_file = filedialog.askopenfilename(
title="Select video", filetypes=(("GoPro MP4 Files", "*.mp4"),))
# open gp file
try:
gp = open(gp_file, "rb")
except FileNotFoundError:
print("file not found")
else:
# read gopro file & find offsets
gp_array = gp.read()
gp.close()
gp_batch_offsets = [match.start() for match in re.finditer(
b'\x47\x59\x52\x4f', gp_array)]
gp_gyro = []
gp_offsets = []
# loop over samples & save gyro readings as gp_gyro[sample][axis]
for batch_offset in gp_batch_offsets:
batch_nof_sample = int.from_bytes(
gp_array[batch_offset + 6:batch_offset + 8], "big")
if batch_nof_sample < 200:
for sample in range(batch_nof_sample):
sample_offset = batch_offset + sample * 6 + 8
x = int.from_bytes(
gp_array[sample_offset:sample_offset + 2], byteorder='big', signed=True)
y = int.from_bytes(
gp_array[sample_offset + 2:sample_offset + 4], byteorder='big', signed=True)
z = int.from_bytes(
gp_array[sample_offset + 4:sample_offset + 6], byteorder='big', signed=True)
gp_gyro.append([x, y, z])
gp_offsets.append(sample_offset)
# clear axis and draw both plots
ax.cla()
bbl_gyro = []
bbl_plot = None
ax.plot([sample[0] for sample in gp_gyro], "g")
plt.draw()
def load_bbl(event):
global bbl_gyro
if len(gp_gyro) == 0:
messagebox.showinfo(
title="Done", message="Please load GoPro file first")
return
# open bbl file
bbl_file = filedialog.askopenfilename(
title="Select bbl", filetypes=(("Blackbox logs", "*.csv"),))
try:
bbl = open(bbl_file)
except FileNotFoundError:
print("file not found")
else:
# find header
gyro_index = None
csv_reader = csv.reader(bbl)
for i, row in enumerate(csv_reader):
if(row[0] == "loopIteration"):
gyro_index = row.index('gyroADC[0]')
break
bbl_df = pd.read_csv(bbl, header=None)
bbl.close()
camera_model = subprocess.getoutput(
"exiftool -s -s -s -FirmwareVersion " + gp_file)[:3]
if camera_model == "HD5":
samplerate = 402
else:
samplerate = 197
bbl_duration = (bbl_df[1].iloc[-1] - bbl_df[1].iloc[0]) / 1000 / 1000
bbl_nof_samples = int(bbl_duration*samplerate)
# downsample & save as bbl_gyro[sample][axis]
bbl_x = np.clip(signal.resample(
[-33*i for i in bbl_df[gyro_index].tolist()], bbl_nof_samples), a_min=-32768, a_max=32767)
bbl_y = np.clip(signal.resample(
[-33*i for i in bbl_df[gyro_index+1].tolist()], bbl_nof_samples), a_min=-32768, a_max=32767)
bbl_z = np.clip(signal.resample(
[33*i for i in bbl_df[gyro_index+2].tolist()], bbl_nof_samples), a_min=-32768, a_max=32767)
bbl_gyro = np.column_stack((bbl_x, bbl_y, bbl_z))
update_frame(0)
draw_bbl_plot()
def patch(event):
# check if both gyros have been loaded
if len(gp_gyro) == 0 or len(bbl_gyro) == 0:
messagebox.showinfo(title="Error", message="Please load files first")
return
# generate and open output_file
output_file = gp_file[:-4] + "_bbl.MP4"
copyfile(gp_file, output_file)
gp = open(output_file, "rb+")
# loop over gp_offsets and write bbl_gyro
for i, sample in enumerate(gp_offsets):
x = int(bbl_frame[i][0])
y = int(bbl_frame[i][1])
z = int(bbl_frame[i][2])
gp.seek(sample)
gp.write(x.to_bytes(2, byteorder='big', signed=True))
gp.seek(sample + 2)
gp.write(y.to_bytes(2, byteorder='big', signed=True))
gp.seek(sample + 4)
gp.write(z.to_bytes(2, byteorder='big', signed=True))
gp.close()
# change metadata to fake a hero6
subprocess.run(['exiftool', '-FirmwareVersion=HD6.01.01.60.00',
'-Model="HERO6 Black"', '-overwrite_original', output_file])
messagebox.showinfo(
title="Done", message="Successfully patched %s" % output_file)
def draw_bbl_plot():
global bbl_plot
ydata = [sample[0] for sample in bbl_frame]
# draw plot
if bbl_plot == None:
bbl_plot, = ax.plot(ydata, "r")
else:
bbl_plot.set_ydata(ydata)
plt.draw()
def update_frame(offset):
global bbl_gyro, bbl_frame
offset = int(offset)
# pad zeros right if the requested frame overflows bbl_gyro
if offset + len(gp_gyro) > len(bbl_gyro):
bbl_gyro = np.concatenate((bbl_gyro, [[0, 0, 0]] * (
offset + len(gp_gyro) - len(bbl_gyro))))
# pad zeros left if offset is negative
if offset < 0:
bbl_frame = np.concatenate(
([[0, 0, 0]] * -offset, bbl_gyro))[:len(gp_gyro)]
else:
bbl_frame = bbl_gyro[offset:offset + len(gp_gyro)]
draw_bbl_plot()
# GUI
load_gp_axis = plt.axes([0.5, 0.05, 0.125, 0.05])
load_gp_button = Button(load_gp_axis, 'Load GP')
load_gp_button.on_clicked(load_gp)
load_bbl_axis = plt.axes([0.65, 0.05, 0.125, 0.05])
load_bbl_button = Button(load_bbl_axis, 'Load BBL')
load_bbl_button.on_clicked(load_bbl)
patch_axis = plt.axes([0.8, 0.05, 0.1, 0.05])
patch_btn = Button(patch_axis, 'Patch')
patch_btn.on_clicked(patch)
offset_axis = plt.axes([0.125, 0.125, 0.775, 0.05])
offset_slider = Slider(offset_axis, 'Offset', -1000, 2000, valstep=1)
offset_slider.on_changed(update_frame)
plt.show()