├── LICENSE ├── README.md ├── SloMoConnectionManager.py ├── __init__.py ├── process-video.sh ├── run-capture.sh ├── slomo_client.py └── slomo_server.py /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2019 Robert Elder Software Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # PatientTurtle, A Client/Server App for Raspberry Pi Slow-Motion Videos 3 | 4 | This purpose of this software is to create a simple client/server application that makes it easier to capture and download data from a Raspberry Pi. The Raspberry Pi memory is easily exaughsted, and the post-processing steps are much faster if they are done off of the Raspberry Pi. This application allows one to remotely request a capture, then download all the data to your desktop and post-process the video there. All the images frames and some metadata (such as the exact capture command used, and final memory usage) are collected into a single .tar file that is sent back to the client for easy processing. 5 | 6 | ## Documentation/Installation 7 | 8 | For information on how to get started recording high speed videos on Raspberry Pi, see this blog post: 9 | 10 | [A Guide to Recording 660FPS Video On A $6 Raspberry Pi Camera](https://blog.robertelder.org/recording-660-fps-on-raspberry-pi-camera/) 11 | 12 | Note that the example scripts in this repo have a lot of hard-coded paths. This is an early prototype and not well tested! 13 | 14 | ## Example Usage 15 | 16 | Once you have dcraw and raspiraw downloaded and built, you can launch the server on the Raspberry Pi: 17 | 18 | ``` 19 | python3 slomo_server.py 20 | ``` 21 | 22 | Then, on your desktop client, edit the file 'slomo_client.py' to use the IP address your Raspberry Pi. Now you can do: 23 | 24 | ``` 25 | python3 slomo_client.py 26 | ``` 27 | 28 | Now, all the video capture data should be stored in a tar file in the current directory. 29 | 30 | ## Post-Processing Videos 31 | 32 | Now you can do post-processing on the contents of the tar to obtain the final video. These steps assume that you have the dcraw repo at ~/dcraw and the compiled executable located at ~/dcraw/dcraw. Here is an example of processing one video: 33 | 34 | ``` 35 | mkdir /dev/shm/slomo 36 | cd /dev/shm/slomo 37 | /capture-location/2019-08-19_14-44-36.692878.tar ./ 38 | tar -xvf 2019-08-19_14-44-36.692878.tar 39 | ./process-video.sh 40 | The processed video is now located at output.mp4 41 | ``` 42 | 43 | The 'process-video.sh' may need to be edited if you have a different version of ffmpeg installed. 44 | 45 | ## TODOs: 46 | 47 | - Support real-time video feed to make it easier to know what the camera currently sees. 48 | - Support more message types to get more visibility into what is happening on the Pi. 49 | - More checking for error conditions instead of always assuming success. 50 | - Review internals of raspiraw and see if there might be a way to do continuous recording. 51 | -------------------------------------------------------------------------------- /SloMoConnectionManager.py: -------------------------------------------------------------------------------- 1 | import socket 2 | import select 3 | import sys 4 | import struct 5 | import json 6 | import os 7 | import traceback 8 | 9 | if sys.version_info < (3, 0): 10 | sys.stdout.write("I only tested this script with python 3, so just to be safe use that.\n") 11 | sys.exit(1) 12 | 13 | SLOMO_HELLO_MESSAGE = 1 14 | 15 | class SloMoMessage(object): 16 | def __init__(self, o, b=bytearray()): 17 | self.o = o 18 | self.b = b 19 | 20 | def pack_to_binary(self): 21 | obj_enc = bytearray(json.dumps(self.o).encode()) 22 | return struct.pack("II", len(obj_enc), len(self.b)) + obj_enc + self.b 23 | 24 | def get_message_object(self): 25 | return json.loads(self.o.decode("utf-8")) 26 | 27 | class SloMoConnectionManager(object): 28 | def __init__(self, debug=False, sigint_callback=None): 29 | self.sigint_callback = sigint_callback 30 | self.recv_size = 1048576 31 | self.EXCEPTION_FLAGS = select.POLLERR 32 | self.READ_FLAGS = select.POLLHUP | select.POLLIN | select.POLLPRI 33 | self.WRITE_FLAGS = select.POLLOUT 34 | self.debug = debug 35 | self.socket_map = {} 36 | self.poller = select.poll() 37 | self.class_callbacks = { 38 | 'close' : {}, 39 | 'read' : {}, 40 | 'write' : {}, 41 | 'exception' : {} 42 | } 43 | 44 | def sfno(self, s): 45 | # Safe fileno function that doesn't casuse exceptions. 46 | try: 47 | fno = s.fileno() 48 | if fno < 0: 49 | return None 50 | else: 51 | return fno 52 | except Exception as e: 53 | return None 54 | 55 | def cleanup(self): 56 | sys.stdout.write("Shutting down closing all %u sockets.\n" % (len(self.socket_map))) 57 | for s in self.socket_map: 58 | try: 59 | sys.stdout.write("Closing fd %u.\n" % (s)) 60 | self.socket_map[s]['socket'].close() 61 | except Exception as e: 62 | pass 63 | 64 | if self.sigint_callback is not None: 65 | sigint_callback() 66 | 67 | def register_file_descriptor(self, fd, classes): 68 | initial_event_mask = self.READ_FLAGS | self.EXCEPTION_FLAGS 69 | self.poller.register(fd, initial_event_mask) 70 | print("Registered file descriptor fd " + str(fd)) 71 | self.socket_map[fd] = { 72 | 'is_listen_socket': False, 73 | 'is_socket': False, 74 | 'event_mask': initial_event_mask, 75 | 'out_bytes': bytearray(b''), 76 | 'in_bytes': bytearray(b''), 77 | 'socket': None, 78 | 'address': None, 79 | 'port': None, 80 | 'classes': classes 81 | } 82 | 83 | def register_listen_socket(self, address, port, classes): 84 | initial_event_mask = self.READ_FLAGS | self.EXCEPTION_FLAGS 85 | listen_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 86 | self.poller.register(self.sfno(listen_socket), initial_event_mask) 87 | print("Registered listen fd " + str(self.sfno(listen_socket))) 88 | self.socket_map[self.sfno(listen_socket)] = { 89 | 'is_listen_socket': True, 90 | 'is_socket': True, 91 | 'event_mask': initial_event_mask, 92 | 'out_bytes': bytearray(b''), 93 | 'in_bytes': bytearray(b''), 94 | 'socket': listen_socket, 95 | 'address': address, 96 | 'port': port, 97 | 'classes': classes 98 | } 99 | listen_socket.bind((address, port)) 100 | listen_socket.listen(10) # Backlog of up to 10 new connections. 101 | 102 | def register_socket(self, sock, address, classes): 103 | initial_event_mask = self.READ_FLAGS | self.WRITE_FLAGS | self.EXCEPTION_FLAGS 104 | self.poller.register(self.sfno(sock), initial_event_mask) 105 | print("Registered socket fd " + str(self.sfno(sock))) 106 | self.socket_map[self.sfno(sock)] = { 107 | 'is_listen_socket': False, 108 | 'is_socket': True, 109 | 'event_mask': initial_event_mask, 110 | 'out_bytes': bytearray(b''), 111 | 'in_bytes': bytearray(b''), 112 | 'socket': sock, 113 | 'address': address, 114 | 'port': False, 115 | 'classes': classes 116 | } 117 | 118 | def register_class_callback(self, event, cl, cb): 119 | self.class_callbacks[event][cl] = cb 120 | 121 | def do_class_callback_for_event(self, event, fd, socket_details): 122 | # Send out callbacks to anything that subscribed to this event 123 | for c in socket_details['classes']: 124 | if c in self.class_callbacks[event]: 125 | self.class_callbacks[event][c](fd, socket_details) 126 | else: 127 | #print("Error: No registered callback for class " + str(c) + " on event " + str(event)) 128 | pass 129 | 130 | def add_to_write_buffer(self, fd, by): 131 | if fd in self.socket_map: 132 | socket_details = self.socket_map[fd] 133 | socket_details['out_bytes'] += by 134 | socket_details['event_mask'] |= self.WRITE_FLAGS 135 | self.poller.modify(fd, socket_details['event_mask']) 136 | else: 137 | print("fd " + str(fd) + " not known in add_to_write_buffer.") 138 | 139 | def try_remove_message(self, fd): 140 | MIN_MESSAGE_BYTES = 8 # size, plus is_binary flag. 141 | if fd in self.socket_map: 142 | socket_details = self.socket_map[fd] 143 | if (len(socket_details['in_bytes'])) >= MIN_MESSAGE_BYTES: 144 | json_size, binary_size = struct.unpack("II", socket_details['in_bytes'][0:MIN_MESSAGE_BYTES]) 145 | rest = socket_details['in_bytes'][MIN_MESSAGE_BYTES:] 146 | if (len(rest) >= (json_size + binary_size)): 147 | json_bytes = rest[0:json_size] 148 | binary_bytes = rest[json_size:(json_size + binary_size)] 149 | socket_details['in_bytes'] = socket_details['in_bytes'][(MIN_MESSAGE_BYTES + (json_size + binary_size)):] 150 | return SloMoMessage(json_bytes, binary_bytes) 151 | else: 152 | # Not enough bytes to even read the size header. 153 | return None 154 | else: 155 | print("fd " + str(fd) + " not known in try_remove_message.") 156 | return None 157 | 158 | def remove_from_read_buffer(self, fd): 159 | if fd in self.socket_map: 160 | socket_details = self.socket_map[fd] 161 | tmp = socket_details['in_bytes'] 162 | socket_details['in_bytes'] = socket_details['in_bytes'][0:0] 163 | return tmp 164 | else: 165 | print("fd " + str(fd) + " not known in remove_from_read_buffer.") 166 | return bytearray(b'') 167 | 168 | def on_generic_exception(self, fd): 169 | if fd in self.socket_map: 170 | socket_details = self.socket_map[fd] 171 | print("Closing socket " + str(fd) + " due to exception event.") 172 | if fd: 173 | if socket_details['socket']: # Pure file descriptors don't have sockets. 174 | socket_details['socket'].close() 175 | self.do_close(fd, socket_details) 176 | else: 177 | print("Exception on unknown fd " + str(fd) + ".") 178 | 179 | def on_generic_write(self, fd): 180 | if fd in self.socket_map: 181 | socket_details = self.socket_map[fd] 182 | if len(socket_details['out_bytes']) == 0: 183 | socket_details['event_mask'] &= ~self.WRITE_FLAGS 184 | self.poller.modify(fd, socket_details['event_mask']) 185 | else: 186 | if socket_details['is_socket']: # for file descriptors. 187 | try: 188 | send_return = socket_details['socket'].send(socket_details['out_bytes']) 189 | socket_details['out_bytes'] = socket_details['out_bytes'][send_return:] # Remove from start of buffer. 190 | except Exception as e: 191 | print("Closing socket " + str(fd) + " due to send fail.") 192 | if fd: 193 | if socket_details['socket']: # Pure file descriptors don't have sockets. 194 | socket_details['socket'].close() 195 | self.do_close(fd, socket_details) 196 | else: 197 | assert(False) # TODO Not implemented. 198 | else: 199 | print("Write event on unknown fd " + str(fd) + ".") 200 | 201 | def on_generic_read(self, fd): 202 | if fd in self.socket_map: 203 | socket_details = self.socket_map[fd] 204 | if not socket_details['is_listen_socket']: # Listen sockets don't have data waiting to recv. 205 | recv_return = bytearray(b"") 206 | if socket_details['is_socket']: # for file descriptors. 207 | try: 208 | recv_return = socket_details['socket'].recv(self.recv_size) 209 | except Exception as e: 210 | print("e from recv was " + str(e)) 211 | else: 212 | recv_return = bytearray(os.read(fd, self.recv_size)) 213 | if len(recv_return) == 0: 214 | print("Closing socket " + str(fd) + " due to 0 byte read.") 215 | if fd: 216 | if socket_details['socket']: # Pure file descriptors don't have sockets. 217 | socket_details['socket'].close() 218 | self.do_close(fd, socket_details) 219 | else: 220 | socket_details['in_bytes'].extend(recv_return) 221 | else: 222 | print("Write event on unknown fd " + str(fd) + ".") 223 | 224 | def do_close(self, fd, socket_details): 225 | if fd in self.socket_map: 226 | self.poller.unregister(fd) 227 | del self.socket_map[fd] 228 | self.do_class_callback_for_event('close', fd, socket_details) 229 | 230 | def run(self, poll_timeout): 231 | if self.debug: 232 | print("Before poller.poll") 233 | try: 234 | events = self.poller.poll(poll_timeout) 235 | for fd, flag in events: 236 | socket_details = self.socket_map[fd] 237 | if flag & (select.POLLIN | select.POLLPRI | select.POLLHUP): 238 | if self.debug: 239 | print("read event on fd " + str(fd)) 240 | self.on_generic_read(fd) 241 | self.do_class_callback_for_event('read', fd, socket_details) 242 | if flag & (select.POLLOUT): 243 | if self.debug: 244 | print("write event on fd " + str(fd)) 245 | self.on_generic_write(fd) 246 | self.do_class_callback_for_event('write', fd, socket_details) 247 | if flag & (select.POLLERR): 248 | if self.debug: 249 | print("POLLERR event on fd " + str(fd)) 250 | self.on_generic_exception(fd) 251 | self.do_class_callback_for_event('exception', fd, socket_details) 252 | except Exception as e: 253 | print("Caught exception in poll or processing flags: " + str(e)) 254 | traceback.print_exc() 255 | if self.debug: 256 | print("After poller.poll") 257 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/RobertElderSoftware/PatientTurtle/c93c5c8bf0daa581c26b07340eec1427f504a4b1/__init__.py -------------------------------------------------------------------------------- /process-video.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -v 4 | 5 | ls *.raw | while read i; do cat hd0.32k "$i" > "$i".all; done # add headers 6 | ls *.all | while read i; do ~/dcraw/dcraw -f -o 1 -v -6 -T -q 3 -W "$i"; done # Convert to .tiff 7 | cat << EOF > make_concat.py 8 | # Use TS information: 9 | import csv 10 | 11 | slowdownx = float(50) 12 | last_microsecond = 0 13 | with open('tstamps.csv') as csv_file: 14 | csv_reader = csv.reader(csv_file, delimiter=',') 15 | line_count = 0 16 | for row in csv_reader: 17 | current_microsecond = int(row[2]) 18 | if line_count > 0: 19 | print("file 'out.%06d.raw.tiff'\nduration %08f" % (int(row[1]), slowdownx * float(current_microsecond - last_microsecond) / float(1000000))) 20 | line_count += 1 21 | last_microsecond = current_microsecond 22 | EOF 23 | python make_concat.py > ffmpeg_concats.txt 24 | ffmpeg -f concat -safe 0 -i ffmpeg_concats.txt -vcodec libx265 -x265-params lossless -crf 0 -b:v 1M -pix_fmt yuv420p -vf "pad=ceil(iw/2)*2:ceil(ih/2)*2" output.mp4 25 | -------------------------------------------------------------------------------- /run-capture.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -v 4 | 5 | rm -f /dev/shm/run_params.txt 6 | rm -f /dev/shm/*.raw 7 | rm -f /dev/shm/tstamps.csv 8 | rm -f /dev/shm/hd0.32k 9 | rm -f /dev/shm/process-video.sh 10 | cp ./process-video.sh /dev/shm/process-video.sh 11 | /home/pi/fork-raspiraw/camera_i2c > /dev/shm/run_params.txt 12 | echo "${1}" >> /dev/shm/run_params.txt 13 | echo "raspiraw version:" >> /dev/shm/run_params.txt 14 | /home/pi/fork-raspiraw/raspiraw >> /dev/shm/run_params.txt 15 | RASPIRAW_ARGS="${1}" 16 | eval "/home/pi/fork-raspiraw/raspiraw ${RASPIRAW_ARGS}" 17 | echo "Here is memory usage after capture:" >> /dev/shm/run_params.txt 18 | df /dev/shm >> /dev/shm/run_params.txt 19 | -------------------------------------------------------------------------------- /slomo_client.py: -------------------------------------------------------------------------------- 1 | import signal 2 | import binascii 3 | import socket 4 | import struct 5 | import sys 6 | import select 7 | import time 8 | import datetime 9 | from SloMoConnectionManager import SloMoConnectionManager 10 | from SloMoConnectionManager import SloMoMessage 11 | 12 | class SloMoClient(object): 13 | def __init__(self, debug=False): 14 | signal.signal(signal.SIGINT, self.cleanup) 15 | self.done = False 16 | self.connection_manager = SloMoConnectionManager() 17 | self.debug = debug 18 | 19 | host = '192.168.0.120' 20 | port = 3050 21 | self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 22 | self.sock.connect((host, port)) 23 | self.connection_manager.register_socket(self.sock, host, ['message_send_socket']) 24 | self.connection_manager.register_class_callback('read', 'message_send_socket', self.on_server_event_read) 25 | 26 | # The current tar file that includes all capture information. 27 | self.capture_filename = None 28 | self.current_tar_file = None 29 | 30 | def cleanup(self, signum, frame): 31 | sys.stdout.write("Caught signal %s. Shutting down.\n" % (str(signum))) 32 | self.connection_manager.cleanup() 33 | self.done = True 34 | 35 | def run(self): 36 | while not self.done: 37 | self.connection_manager.run(10000) 38 | 39 | def open_new_tar_file(self): 40 | datetimestr = str(datetime.datetime.now()).replace(":","-").replace(" ", "_") 41 | self.capture_filename = datetimestr + ".tar" 42 | self.current_tar_file = open(self.capture_filename, 'w+b') 43 | print("Opened file " + self.capture_filename + " for writing capture output.") 44 | 45 | def close_tar_file(self): 46 | self.current_tar_file.close() 47 | self.current_tar_file = None 48 | print("Closed file " + self.capture_filename + ".") 49 | 50 | def append_byes_to_tar_file(self, by): 51 | self.current_tar_file.write(by) 52 | 53 | def on_server_message(self, m): 54 | #print("MSG: " + m.o.decode("utf-8") + " Binary: " + str(m.b)) 55 | message_object = m.get_message_object() 56 | print(str(message_object)) 57 | if 'end_capture' in message_object: 58 | self.send_request_results() 59 | if 'begin_tar_stream' in message_object: 60 | self.open_new_tar_file() 61 | if 'data' in message_object: 62 | if message_object['data'] == 'tar_output': 63 | self.append_byes_to_tar_file(m.b) 64 | else: 65 | # Not implemented 66 | assert(False) 67 | if 'end_tar_stream' in message_object: 68 | self.close_tar_file() 69 | 70 | def on_server_event_read(self, fd, socket_details): 71 | fd = self.connection_manager.sfno(socket_details['socket']) 72 | if fd: 73 | while True: 74 | m = self.connection_manager.try_remove_message(fd) 75 | if m is None: 76 | break 77 | self.on_server_message(m) 78 | 79 | def send_capture_message(self): 80 | send_fd = self.connection_manager.sfno(self.sock) 81 | if send_fd: 82 | rasipraw_args = [ 83 | "-md", "7", 84 | "-t", "10000", 85 | "-ts", "/dev/shm/tstamps.csv", 86 | "-hd0", "/dev/shm/hd0.32k", 87 | "-h", "64", 88 | "-w", "640", 89 | "--vinc", "1F", 90 | "--fps", "660", 91 | "-sr", "1", 92 | "-o", "/dev/shm/out.%06d.raw" 93 | ] 94 | r = SloMoMessage({'request_capture': rasipraw_args}) 95 | msg = r.pack_to_binary() 96 | self.connection_manager.add_to_write_buffer(send_fd, msg) 97 | print(msg) 98 | else: 99 | print("Did not send message: invlid fd.") 100 | 101 | def send_request_results(self): 102 | send_fd = self.connection_manager.sfno(self.sock) 103 | if send_fd: 104 | r = SloMoMessage({'request_results': {'-fps':123}}) 105 | msg = r.pack_to_binary() 106 | self.connection_manager.add_to_write_buffer(send_fd, msg) 107 | print(msg) 108 | else: 109 | print("Did not send message: invlid fd.") 110 | 111 | s = SloMoClient() 112 | s.send_capture_message() 113 | s.run() 114 | -------------------------------------------------------------------------------- /slomo_server.py: -------------------------------------------------------------------------------- 1 | import time 2 | import sys 3 | import socket 4 | import select 5 | import subprocess 6 | from SloMoConnectionManager import SloMoConnectionManager 7 | from SloMoConnectionManager import SloMoMessage 8 | import signal 9 | 10 | class SloMoServer(object): 11 | def __init__(self, debug): 12 | self.done = False 13 | 14 | signal.signal(signal.SIGINT, self.cleanup) 15 | 16 | self.connection_manager = SloMoConnectionManager(debug=debug) 17 | 18 | self.connection_manager.register_listen_socket('0.0.0.0', 3050, ['client_listen_socket']) 19 | self.connection_manager.register_class_callback('read', 'client_listen_socket', self.on_client_listen_socket_connect) 20 | self.capture_child = None # child process for capture command. 21 | self.tar_child = None # child process for tar command. 22 | self.client_socket = None # Assume we can only support one client at once. 23 | 24 | def cleanup(self, signum, frame): 25 | sys.stdout.write("Caught signal %s. Shutting down.\n" % (str(signum))) 26 | self.connection_manager.cleanup() 27 | self.done = True 28 | 29 | def on_client_close(self, fd, socket_details): 30 | self.client_socket = None 31 | 32 | def on_client_listen_socket_connect(self, fd, socket_details): 33 | conn, addr = socket_details['socket'].accept() 34 | self.connection_manager.register_socket(conn, addr, ['client']) 35 | self.connection_manager.register_class_callback('read', 'client', self.on_client_event_read) 36 | self.connection_manager.register_class_callback('close', 'client', self.on_client_close) 37 | assert(self.client_socket == None) 38 | self.client_socket = conn 39 | assert(self.client_socket != None) 40 | 41 | def send_capture_finished_message(self, send_fd, o): 42 | if send_fd: 43 | r = SloMoMessage({'capture_finished': o}) 44 | msg = r.pack_to_binary() 45 | self.connection_manager.add_to_write_buffer(send_fd, msg) 46 | print(msg) 47 | else: 48 | print("Did not send capture_finished message: invlid fd.") 49 | 50 | def on_capture_command_stdout_close(self, fd, socket_details): 51 | print("Do capture close message: " + str(fd)) 52 | client_fd = self.connection_manager.sfno(self.client_socket) 53 | r = SloMoMessage({'end_capture': True}) 54 | msg = r.pack_to_binary() 55 | self.connection_manager.add_to_write_buffer(client_fd, msg) 56 | 57 | def on_capture_command_stdout(self, fd, socket_details): 58 | sys.stdout.write(str(socket_details['in_bytes'].decode('utf-8'))) 59 | socket_details['in_bytes'] = bytearray(b'') 60 | 61 | def on_capture_command_stderr(self, fd, socket_details): 62 | sys.stdout.write(str(socket_details['in_bytes'].decode('utf-8'))) 63 | socket_details['in_bytes'] = bytearray(b'') 64 | 65 | def do_capture(self, m): 66 | print("Doing capture results.") 67 | try: 68 | client_fd = self.connection_manager.sfno(self.client_socket) 69 | r = SloMoMessage({'begin_capture': True}) 70 | msg = r.pack_to_binary() 71 | self.connection_manager.add_to_write_buffer(client_fd, msg) 72 | 73 | cmd_arr = ["./run-capture.sh", " ".join(m['request_capture'])] 74 | self.capture_child = subprocess.Popen(cmd_arr, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 75 | stdout_fd = self.capture_child.stdout.fileno() 76 | stderr_fd = self.capture_child.stderr.fileno() 77 | 78 | self.connection_manager.register_file_descriptor(stdout_fd, ['capture_command_stdout']) 79 | self.connection_manager.register_class_callback('read', 'capture_command_stdout', self.on_capture_command_stdout) 80 | self.connection_manager.register_class_callback('close', 'capture_command_stdout', self.on_capture_command_stdout_close) 81 | self.connection_manager.register_file_descriptor(stderr_fd, ['capture_command_stderr']) 82 | self.connection_manager.register_class_callback('read', 'capture_command_stderr', self.on_capture_command_stderr) 83 | except Exception as e: 84 | print("An exception happend when trying to run command: " + str(cmd_arr) + " " + str(e) + "\n") 85 | return "" 86 | 87 | def on_tar_command_stdout_close(self, fd, socket_details): 88 | client_fd = self.connection_manager.sfno(self.client_socket) 89 | r = SloMoMessage({'end_tar_stream': True}) 90 | msg = r.pack_to_binary() 91 | self.connection_manager.add_to_write_buffer(client_fd, msg) 92 | 93 | def on_tar_command_stdout(self, fd, socket_details): 94 | #sys.stdout.write(str(socket_details['in_bytes'])) 95 | client_fd = self.connection_manager.sfno(self.client_socket) 96 | if len(socket_details['in_bytes']) > 0: 97 | r = SloMoMessage({'data': 'tar_output'}, socket_details['in_bytes']) 98 | msg = r.pack_to_binary() 99 | self.connection_manager.add_to_write_buffer(client_fd, msg) 100 | socket_details['in_bytes'] = bytearray(b'') 101 | 102 | def on_tar_command_stderr(self, fd, socket_details): 103 | sys.stdout.write(str(socket_details['in_bytes'].decode('utf-8'))) 104 | socket_details['in_bytes'] = bytearray(b'') 105 | 106 | def tar_out_results(self, m): 107 | print("Doing tar out results.") 108 | try: 109 | client_fd = self.connection_manager.sfno(self.client_socket) 110 | print("client_fd is " + str(client_fd)) 111 | r = SloMoMessage({'begin_tar_stream': True}) 112 | msg = r.pack_to_binary() 113 | self.connection_manager.add_to_write_buffer(client_fd, msg) 114 | 115 | cmd = "cd /dev/shm && tar -cf /dev/stdout *.raw hd0.32k tstamps.csv run_params.txt process-video.sh" 116 | self.tar_child = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) 117 | stdout_fd = self.tar_child.stdout.fileno() 118 | stderr_fd = self.tar_child.stderr.fileno() 119 | 120 | self.connection_manager.register_file_descriptor(stdout_fd, ['tar_command_stdout']) 121 | self.connection_manager.register_class_callback('read', 'tar_command_stdout', self.on_tar_command_stdout) 122 | self.connection_manager.register_class_callback('close', 'tar_command_stdout', self.on_tar_command_stdout_close) 123 | self.connection_manager.register_file_descriptor(stderr_fd, ['tar_command_stderr']) 124 | self.connection_manager.register_class_callback('read', 'tar_command_stderr', self.on_tar_command_stderr) 125 | except Exception as e: 126 | print("An exception happend when trying to run command: " + str(cmd_arr) + " " + str(e) + "\n") 127 | return "" 128 | 129 | def on_client_message(self, fd, m): 130 | print("Server got message: " + str(m)) 131 | if 'request_capture' in m: 132 | o = self.do_capture(m) 133 | if 'request_results' in m: 134 | o = self.tar_out_results(m) 135 | 136 | def on_client_event_read(self, fd, socket_details): 137 | fd = self.connection_manager.sfno(socket_details['socket']) 138 | if fd: 139 | while True: 140 | m = self.connection_manager.try_remove_message(fd) 141 | if m is None: 142 | break 143 | self.on_client_message(fd, m.get_message_object()) 144 | 145 | def run(self): 146 | while not self.done: 147 | self.connection_manager.run(10000) 148 | 149 | 150 | s = SloMoServer(debug=False) 151 | s.run() 152 | --------------------------------------------------------------------------------