def run(self):
        with PiCamera() as camera:
            count = 0
            camera.resolution = (1280, 720)
            stream = PiCameraCircularIO(camera, seconds=10)
            camera.start_recording(stream, format='h264')
            try:
                camera.wait_recording(3)
                while True:
                    camera.wait_recording(0)
                    if self.detect_motion(camera):
                        print('Motion detected!')

                        if not self.dryrun:
                            timestamp = datetime.now().isoformat('_')

                            # As soon as we detect motion, split the recording to
                            # record the frames "after" motion
                            camera.split_recording(
                                '{}_capture_{:04d}.h264'.format(
                                    timestamp, count))
                            # Write the 10 seconds "before" motion to disk as well
                            stream.copy_to('{}_before_{:04d}.h264'.format(
                                timestamp, count),
                                           seconds=10)

                        stream.clear()

                        # Wait until motion is no longer detected, then split
                        # recording back to the in-memory circular buffer
                        while self.detect_motion(camera):
                            camera.wait_recording(0)

                        print('Motion stopped!')
                        camera.split_recording(stream)

                        count += 1

                    if user_freespace_gb() < 0.5:
                        print("CLosing program to avoid using all free space.")
                        print("There is {} GB remaining.".format(
                            user_freespace_gb()))
                        break

            except KeyboardInterrupt:
                print("Stopped")

            finally:
                camera.stop_recording()

                print("Motion detection values:")
                print("\tMin: {}".format(self.result_min))
                print("\tMax: {}".format(self.result_max))
                print("\tAvg: {}".format(self.result_avg))
Example #2
0
class Recorder:
    def __init__(self,
                 camera,
                 storage,
                 h264_args,
                 temporary_recordings_output_path="./temp_recordings/",
                 record_seconds_after_motion=12,
                 max_recording_seconds=600,
                 record_seconds_before_motion=5,
                 ffmpeg_path="/usr/local/bin/ffmpeg",
                 convert_h264_to_mp4=True):
        self.camera = camera
        self.storage = storage
        self.h264_args = h264_args
        self.temporary_recordings_output_path = temporary_recordings_output_path
        self.record_seconds_after_motion = record_seconds_after_motion
        self.max_recording_seconds = max_recording_seconds
        self.timer = 0
        self.record_seconds_before_motion = record_seconds_before_motion
        self.ffmpeg_path = ffmpeg_path
        self.convert_h264_to_mp4 = convert_h264_to_mp4

        # Make sure PiCameraCircularIO contains at least 20 seconds of footage. Since this is the minimum for it work.
        if record_seconds_before_motion > 20:
            delayed_storage_length_seconds = record_seconds_before_motion
        else:
            delayed_storage_length_seconds = 20
        # Create the delayed frames stream.
        self.delayed_recording_stream = PiCameraCircularIO(
            self.camera, seconds=delayed_storage_length_seconds)
        # For some reason the PiCameraCircularIO has to be on splitter_port 1. Splitter port 2 or 3 doesn't work.
        self.camera.start_recording(self.delayed_recording_stream,
                                    splitter_port=1,
                                    **h264_args)

    # Method to call when there is motion.
    # This will start the recording if it hadn't already been started.
    # Extend the recording if the recording has already started.
    def report_motion(self):
        if self.timer == 0:
            self.timer = self.record_seconds_after_motion
            self._start_recording()
        else:
            self.timer = self.record_seconds_after_motion

    # Starts the recording.
    def _start_recording(self):
        # Create the filename and path.
        current_time_string = str(datetime.datetime.now())[11:13] + "-" + str(datetime.datetime.now())[14:16] \
                              + '-' + str(datetime.datetime.now())[17:19]
        if not os.path.isdir(
                os.path.join(get_exec_dir(),
                             self.temporary_recordings_output_path)):
            os.mkdir(
                os.path.join(get_exec_dir(),
                             self.temporary_recordings_output_path))
        output_file_name = os.path.join(get_exec_dir(),
                                        self.temporary_recordings_output_path,
                                        current_time_string)
        print('Started recording ' + output_file_name)

        # record the frames "after" motion
        self.camera.split_recording(output_file_name + '_after.h264',
                                    splitter_port=1,
                                    seconds=10)
        # Write the 10 seconds "before" motion to disk as well
        self.delayed_recording_stream.copy_to(
            output_file_name + '_before.h264',
            seconds=self.record_seconds_before_motion)
        # Clear the delayed recording stream.
        self.delayed_recording_stream.clear()

        threading.Thread(target=self._start_countdown,
                         args=(output_file_name, ),
                         daemon=True).start()

    # Starts counting down from record_seconds_after_movement after movement is detected.
    # Stop recording if the timer gets to 0.
    def _start_countdown(self, output_file_name):
        self.timer = self.record_seconds_after_motion
        recorded_time = 0
        while self.timer > 0 and not recorded_time > self.max_recording_seconds:
            time.sleep(1)
            recorded_time += 1
            self.timer -= 1
        # split the recording back to the delayed frames stream.
        self.camera.split_recording(self.delayed_recording_stream,
                                    splitter_port=1)
        # Merge the two recordings.
        file_path = self._merge_recordings(output_file_name)
        # Put the h264 recording into an mp4 container.
        if self.convert_h264_to_mp4:
            file_path = self._put_in_mp4_container(file_path)
        # Store the recording in the right place.
        self.storage.store(file_path)

    # Merge the two h264 recordings and delete the old h264 files.
    def _merge_recordings(self, output_file_name):
        with open(output_file_name + "_before.h264", 'rb') as before:
            with open(output_file_name + "_after.h264", 'rb') as after:
                with open(output_file_name + ".h264", 'ab') as new:
                    new.write(before.read())
                    new.write(after.read())
        # Remove the separate files.
        try:
            os.remove(output_file_name + "_before.h264")
            os.remove(output_file_name + "_after.h264")
        except Exception as e:
            print(e)
        return output_file_name + ".h264"

    # Put the h264 recording into an mp4 container.
    def _put_in_mp4_container(self, file_path):
        output_file_path = file_path.replace("h264", "mp4")
        # ffmpeg -i "before.h264" -c:v copy -f mp4 "myOutputFile.mp4"
        subprocess.call([
            '{}'.format(self.ffmpeg_path), '-i', '{}'.format(file_path),
            '-c:v', 'copy', '-f', 'mp4', '{}'.format(output_file_path)
        ],
                        stdin=subprocess.PIPE)
        # Remove h264 file
        try:
            os.remove(file_path)
        except Exception as e:
            print(e)
        return output_file_path