-
Notifications
You must be signed in to change notification settings - Fork 0
/
video2.py
272 lines (242 loc) · 8.47 KB
/
video2.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
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
"""
tellopy sample using keyboard and video player
Requires mplayer to record/save video.
Controls:
- tab to lift off
- WASD to move the drone
- space/shift to ascend/descent slowly
- Q/E to yaw slowly
- arrow keys to ascend, descend, or yaw quickly
- backspace to land, or P to palm-land
- enter to take a picture
- R to start recording video, R again to stop recording
(video and photos will be saved to a timestamped file in ~/Pictures/)
- Z to toggle camera zoom state
(zoomed-in widescreen or high FOV 4:3)
"""
import time
import sys
import tellopy
import pygame
import pygame.display
import pygame.key
import pygame.locals
import pygame.font
import os
import datetime
from subprocess import Popen, PIPE
# from tellopy import logger
# log = tellopy.logger.Logger('TelloUI')
prev_flight_data = None
video_player = None
video_recorder = None
font = None
wid = None
date_fmt = '%Y-%m-%d_%H%M%S'
def toggle_recording(drone, speed):
global video_recorder
global date_fmt
if speed == 0:
return
if video_recorder:
# already recording, so stop
video_recorder.stdin.close()
status_print('Video saved to %s' % video_recorder.video_filename)
video_recorder = None
return
# start a new recording
filename = '%s/Pictures/tello-%s.mp4' % (os.getenv('HOME'),
datetime.datetime.now().strftime(date_fmt))
video_recorder = Popen([
'mencoder', '-', '-vc', 'x264', '-fps', '30', '-ovc', 'copy',
'-of', 'lavf', '-lavfopts', 'format=mp4',
# '-ffourcc', 'avc1',
# '-really-quiet',
'-o', filename,
], stdin=PIPE)
video_recorder.video_filename = filename
status_print('Recording video to %s' % filename)
def take_picture(drone, speed):
if speed == 0:
return
drone.take_picture()
def palm_land(drone, speed):
if speed == 0:
return
drone.palm_land()
def toggle_zoom(drone, speed):
# In "video" mode the drone sends 1280x720 frames.
# In "photo" mode it sends 2592x1936 (952x720) frames.
# The video will always be centered in the window.
# In photo mode, if we keep the window at 1280x720 that gives us ~160px on
# each side for status information, which is ample.
# Video mode is harder because then we need to abandon the 16:9 display size
# if we want to put the HUD next to the video.
if speed == 0:
return
drone.set_video_mode(not drone.zoom)
pygame.display.get_surface().fill((0,0,0))
pygame.display.flip()
controls = {
'w': 'forward',
's': 'backward',
'a': 'left',
'd': 'right',
'space': 'up',
'left shift': 'down',
'right shift': 'down',
'q': 'counter_clockwise',
'e': 'clockwise',
# arrow keys for fast turns and altitude adjustments
'left': lambda drone, speed: drone.counter_clockwise(speed*2),
'right': lambda drone, speed: drone.clockwise(speed*2),
'up': lambda drone, speed: drone.up(speed*2),
'down': lambda drone, speed: drone.down(speed*2),
'tab': lambda drone, speed: drone.takeoff(),
'backspace': lambda drone, speed: drone.land(),
'p': palm_land,
'r': toggle_recording,
'z': toggle_zoom,
'enter': take_picture,
'return': take_picture,
}
class FlightDataDisplay(object):
# previous flight data value and surface to overlay
_value = None
_surface = None
# function (drone, data) => new value
# default is lambda drone,data: getattr(data, self._key)
_update = None
def __init__(self, key, format, colour=(255,255,255), update=None):
self._key = key
self._format = format
self._colour = colour
if update:
self._update = update
else:
self._update = lambda drone,data: getattr(data, self._key)
def update(self, drone, data):
new_value = self._update(drone, data)
if self._value != new_value:
self._value = new_value
self._surface = font.render(self._format % (new_value,), True, self._colour)
return self._surface
def flight_data_mode(drone, *args):
return (drone.zoom and "VID" or "PIC")
def flight_data_recording(*args):
return (video_recorder and "REC 00:00" or "") # TODO: duration of recording
def update_hud(hud, drone, flight_data):
(w,h) = (158,0) # width available on side of screen in 4:3 mode
blits = []
for element in hud:
surface = element.update(drone, flight_data)
if surface is None:
continue
blits += [(surface, (0, h))]
# w = max(w, surface.get_width())
h += surface.get_height()
h += 64 # add some padding
overlay = pygame.Surface((w, h), pygame.SRCALPHA)
overlay.fill((0,0,0)) # remove for mplayer overlay mode
for blit in blits:
overlay.blit(*blit)
pygame.display.get_surface().blit(overlay, (0,0))
pygame.display.update(overlay.get_rect())
def status_print(text):
pygame.display.set_caption(text)
hud = [
FlightDataDisplay('height', 'ALT %3d'),
FlightDataDisplay('ground_speed', 'SPD %3d'),
FlightDataDisplay('battery_percentage', 'BAT %3d%%'),
FlightDataDisplay('wifi_strength', 'NET %3d%%'),
FlightDataDisplay(None, 'CAM %s', update=flight_data_mode),
FlightDataDisplay(None, '%s', colour=(255, 0, 0), update=flight_data_recording),
]
def flightDataHandler(event, sender, data):
global prev_flight_data
text = str(data)
if prev_flight_data != text:
update_hud(hud, sender, data)
prev_flight_data = text
def videoFrameHandler(event, sender, data):
global video_player
global video_recorder
if video_player is None:
cmd = [ 'mplayer', '-fps', '35', '-really-quiet' ]
if wid is not None:
cmd = cmd + [ '-wid', str(wid) ]
video_player = Popen(cmd + ['-'], stdin=PIPE)
try:
video_player.stdin.write(data)
except IOError as err:
status_print(str(err))
video_player = None
try:
if video_recorder:
video_recorder.stdin.write(data)
except IOError as err:
status_print(str(err))
video_recorder = None
def handleFileReceived(event, sender, data):
global date_fmt
# Create a file in ~/Pictures/ to receive image data from the drone.
path = '%s/Pictures/tello-%s.jpeg' % (
os.getenv('HOME'),
datetime.datetime.now().strftime('%Y-%m-%d_%H%M%S'))
with open(path, 'wb') as fd:
fd.write(data)
status_print('Saved photo to %s' % path)
def main():
pygame.init()
pygame.display.init()
pygame.display.set_mode((1280, 720))
pygame.font.init()
global font
font = pygame.font.SysFont("dejavusansmono", 32)
global wid
if 'window' in pygame.display.get_wm_info():
wid = pygame.display.get_wm_info()['window']
print("Tello video WID:", wid)
drone = tellopy.Tello()
drone.connect()
drone.start_video()
drone.subscribe(drone.EVENT_FLIGHT_DATA, flightDataHandler)
drone.subscribe(drone.EVENT_VIDEO_FRAME, videoFrameHandler)
drone.subscribe(drone.EVENT_FILE_RECEIVED, handleFileReceived)
speed = 30
try:
while 1:
time.sleep(0.01) # loop with pygame.event.get() is too mush tight w/o some sleep
for e in pygame.event.get():
# WASD for movement
if e.type == pygame.locals.KEYDOWN:
print('+' + pygame.key.name(e.key))
keyname = pygame.key.name(e.key)
if keyname == 'escape':
drone.quit()
exit(0)
if keyname in controls:
key_handler = controls[keyname]
if type(key_handler) == str:
getattr(drone, key_handler)(speed)
else:
key_handler(drone, speed)
elif e.type == pygame.locals.KEYUP:
print('-' + pygame.key.name(e.key))
keyname = pygame.key.name(e.key)
if keyname in controls:
key_handler = controls[keyname]
if type(key_handler) == str:
getattr(drone, key_handler)(0)
else:
key_handler(drone, 0)
except e:
print(str(e))
finally:
print('Shutting down connection to drone...')
if video_recorder:
toggle_recording(drone, 1)
drone.quit()
exit(1)
if __name__ == '__main__':
main()