From e3d0757a2c93a41c713b631b3f5740ad3e52b09f Mon Sep 17 00:00:00 2001 From: langolierz Date: Tue, 2 Oct 2018 18:05:11 +0000 Subject: [PATCH] created seperate class for of_capture so they do not compete for access to camera etc --- actions.py | 20 ++- display_centre/display.py | 21 ++- json_objects/keypad_action_mapping.json | 4 +- json_objects/settings_default.json | 8 + r_e_c_u_r.py | 8 +- video_centre/capture.py | 57 ++++--- video_centre/of_capture.py | 189 ++++++++++++++++++++++++ 7 files changed, 262 insertions(+), 45 deletions(-) create mode 100644 video_centre/of_capture.py diff --git a/actions.py b/actions.py index 38540fb..add2649 100644 --- a/actions.py +++ b/actions.py @@ -9,18 +9,27 @@ from pythonosc import osc_server from pythonosc import dispatcher import threading import argparse +from video_centre.capture import Capture +from video_centre.of_capture import OfCapture class Actions(object): - def __init__(self, tk, message_handler, data, video_driver, capture, shaders, display): + def __init__(self, tk, message_handler, data, video_driver, shaders, display, osc_client): self.tk = tk self.message_handler = message_handler self.data = data self.video_driver = video_driver - self.capture = capture self.shaders = shaders self.display = display + self.osc_client = osc_client + self.create_capture_object('value') self.server = self.setup_osc_server() + def create_capture_object(self, value): + if self.data.settings['other']['USE_OF_CAPTURE']['value'] == 'yes': + self.capture = OfCapture(self.tk, self.osc_client, self.message_handler, self.data) + else: + self.capture = Capture(self.tk, self.message_handler, self.data) + self.display.capture = self.capture def move_browser_selection_down(self): self.display.browser_menu.navigate_menu_down() @@ -192,6 +201,13 @@ class Actions(object): if is_successful and self.video_driver.current_player.status != 'PAUSED': self.video_driver.current_player.toggle_pause() +## these are temp for testing ! + def start_of_capture_preview(self): + self.osc_client.send_message("/capture/start", True) + + def stop_of_capture_preview(self): + self.osc_client.send_message("/capture/stop", True) + def toggle_capture_recording(self): is_recording = self.capture.is_recording if is_recording: diff --git a/display_centre/display.py b/display_centre/display.py index 05266f5..627af31 100644 --- a/display_centre/display.py +++ b/display_centre/display.py @@ -9,10 +9,10 @@ class Display(object): ROW_OFFSET = 6.0 TITLE = '================== r_e_c_u_r ==================' - def __init__(self, tk, video_driver, capture, shaders, message_handler, data): + def __init__(self, tk, video_driver, shaders, message_handler, data): self.tk = tk self.video_driver = video_driver - self.capture = capture + self.capture = None self.shaders = shaders self.message_handler = message_handler self.data = data @@ -198,9 +198,17 @@ class Display(object): def _get_status_for_player(self): now_slot, now_status, now_alpha, next_slot, next_status, next_alpha = self.video_driver.get_player_info_for_status() - capture_status = self._generate_capture_status() + if self.capture is not None: + capture_status = self._generate_capture_status() + preview_alpha = self.capture.get_preview_alpha() + else: + capture_status = '' + preview_alpha = 0 - preview_alpha = self.capture.get_preview_alpha() + + if preview_alpha == None: + preview_alpha = 0 + #print('capture alpha is {}'.format(preview_alpha)) self._set_colour_from_alpha(now_alpha, preview_alpha, next_alpha) @@ -260,7 +268,10 @@ class Display(object): def _set_colour_from_alpha(self, now_alpha, preview_alpha, next_alpha): upper_bound = 150 - is_recording = self.capture.is_recording == True + if self.capture is not None: + is_recording = self.capture.is_recording == True + else: + is_recording = False ### scale values scaled_now = int(( now_alpha / 255 ) * (255 - upper_bound) + upper_bound) scaled_preview = int(( preview_alpha / 255 ) * (255 - upper_bound) + upper_bound) diff --git a/json_objects/keypad_action_mapping.json b/json_objects/keypad_action_mapping.json index 1e13b87..796350f 100644 --- a/json_objects/keypad_action_mapping.json +++ b/json_objects/keypad_action_mapping.json @@ -59,10 +59,10 @@ "DEFAULT": ["load_slot_6_into_next_player","toggle_shaders"] }, "q": { - "DEFAULT": ["load_slot_7_into_next_player", "increase_speed"] + "DEFAULT": ["load_slot_7_into_next_player", "start_of_capture_preview"] }, "r": { - "DEFAULT": ["load_slot_8_into_next_player", "decrease_speed"] + "DEFAULT": ["load_slot_8_into_next_player", "stop_of_capture_preview"] }, "s": { "DEFAULT": ["load_slot_9_into_next_player","quit_the_program"] diff --git a/json_objects/settings_default.json b/json_objects/settings_default.json index e6942fb..14c2cf5 100644 --- a/json_objects/settings_default.json +++ b/json_objects/settings_default.json @@ -131,6 +131,14 @@ ], "value": "dev" }, + "USE_OF_CAPTURE": { + "action": "create_capture_object", + "options": [ + "yes", + "no" + ], + "value": "no" + }, "VIDEO_BACKEND": { "action": "switch_video_backend", "options": [ diff --git a/r_e_c_u_r.py b/r_e_c_u_r.py index c6bd985..40cc398 100755 --- a/r_e_c_u_r.py +++ b/r_e_c_u_r.py @@ -15,7 +15,7 @@ from user_input.numpad_input import NumpadInput from user_input.midi_input import MidiInput from user_input.analog_input import AnalogInput from video_centre.video_driver import VideoDriver -from video_centre.capture import Capture +#from video_centre.capture import Capture from video_centre.shaders import Shaders import data_centre @@ -43,14 +43,14 @@ def setup_osc_client(): osc_client = setup_osc_client() # setup the video driver video_driver = VideoDriver(tk, osc_client, message_handler, data) -capture = Capture(tk, osc_client, message_handler, data) +#capture = Capture(tk, osc_client, message_handler, data) shaders = Shaders(tk, osc_client, message_handler, data) # setup the display -display = Display(tk, video_driver, capture, shaders, message_handler, data) +display = Display(tk, video_driver, shaders, message_handler, data) # setup the actions -actions = Actions(tk, message_handler, data, video_driver, capture, shaders, display) +actions = Actions(tk, message_handler, data, video_driver, shaders, display, osc_client) numpad_input = NumpadInput(tk, message_handler, display, actions, data) midi_input = MidiInput(tk, message_handler, display, actions, data) diff --git a/video_centre/capture.py b/video_centre/capture.py index a78c0da..e8c5f56 100644 --- a/video_centre/capture.py +++ b/video_centre/capture.py @@ -7,9 +7,8 @@ import fractions class Capture(object): PREVIEW_LAYER = 255 - def __init__(self, root, osc_client, message_handler, data): + def __init__(self, root, message_handler, data): self.root = root - self.osc_client = osc_client self.message_handler = message_handler self.data = data @@ -57,14 +56,19 @@ class Capture(object): if self.use_capture == False: self.message_handler.set_message('INFO', 'capture not enabled') return False - if not self.device or self.device.closed: - self.create_capture_device() - self.is_previewing = True - self.device.start_preview() - self.set_preview_screen_size() - self.set_capture_settings() - self.device.preview.layer = self.PREVIEW_LAYER - return True + if self.data.settings['other']['USE_OF_CAPTURE']['value'] == 'yes': + self.osc_client.send_message("/capture/start", True) + self.is_previewing = True + return True + else: + if not self.device or self.device.closed: + self.create_capture_device() + self.is_previewing = True + self.device.start_preview() + self.set_preview_screen_size() + self.set_capture_settings() + self.device.preview.layer = self.PREVIEW_LAYER + return True def set_capture_settings(self): if self.capture_type == "piCaptureSd1": @@ -84,10 +88,14 @@ class Capture(object): self.device.preview.fullscreen = True def stop_preview(self): - self.device.stop_preview() - self.is_previewing = False - if not self.device.recording: - self.device.close() + if self.data.settings['other']['USE_OF_CAPTURE']['value'] == 'yes': + self.osc_client.send_message("/capture/stop", True) + self.is_previewing = False + else: + self.device.stop_preview() + self.is_previewing = False + if not self.device.recording: + self.device.close() def start_recording(self): if self.use_capture == False: @@ -95,7 +103,7 @@ class Capture(object): return if not self.check_available_disk_space(): return - if self.device.closed: + if self.device is None or self.device.closed: self.create_capture_device() if not os.path.exists(self.video_dir): @@ -185,22 +193,10 @@ class Capture(object): error_info = e.message else: error_info = e - self.message_handler.set_message('ERROR',error_info) - else: + self.message_handler.set_message('ERROR',error_info) + return 0 return 0 - #def is_previewing(self): - # if self.device.closed or not self.device.preview: - # return False - #else: - # return True - - #def is_recording(self): - # if self.device.recording: - # return True - #else: - # return False - def set_colour(self, u_value, v_value): (u, v) = (128, 128) if self.device.color_effects is not None: @@ -235,6 +231,3 @@ class Capture(object): - - - diff --git a/video_centre/of_capture.py b/video_centre/of_capture.py new file mode 100644 index 0000000..57f964a --- /dev/null +++ b/video_centre/of_capture.py @@ -0,0 +1,189 @@ +import os +import subprocess +import datetime +import fractions + +class OfCapture(object): + def __init__(self, root, osc_client, message_handler, data): + self.root = root + self.osc_client = osc_client + self.message_handler = message_handler + self.data = data + + self.device = None + self.is_recording = False + self.is_previewing = False + self.video_dir = '/home/pi/Videos/' + self.update_capture_settings() + #self.create_capture_device() + + #def create_capture_device(self): + # if self.use_capture: + # self.update_capture_settings() + # try: + # self.device = picamera.PiCamera(resolution=self.resolution, framerate=self.framerate, sensor_mode = self.sensor_mode) + # except picamera.exc.PiCameraError as e: + # self.use_capture = False + # print('camera exception is {}'.format(e)) + # self.message_handler.set_message('INFO', 'no capture device attached') + + def update_capture_settings(self): + ##setting class variables + self.use_capture = self.data.settings['capture']['DEVICE']['value'] == 'enabled' + self.resolution = self.convert_resolution_value(self.data.settings['capture']['RESOLUTION']['value']) + self.framerate = self.convert_framerate_value(self.data.settings['capture']['FRAMERATE']['value']) + self.capture_type = self.data.settings['capture']['TYPE']['value'] + if self.capture_type == "piCaptureSd1": + self.sensor_mode = 6 + else: + self.sensor_mode = 0 + ##update current instance (device) if in use + if self.device and not self.device.closed: + self.device.image_effect = self.data.settings['capture']['IMAGE_EFFECT']['value'] + self.device.shutter_speed = self.convert_shutter_value(self.data.settings['capture']['SHUTTER']['value']) + ## can only update resolution and framerate if not recording + if not self.is_recording: + self.device.framerate = self.framerate + self.device.resolution = self.resolution + + + def start_preview(self): + if self.use_capture == False: + self.message_handler.set_message('INFO', 'capture not enabled') + return False + + self.is_previewing = True + self.set_capture_settings() + self.osc_client.send_message("/capture/preview/start", True) + return True + + def set_capture_settings(self): + if self.capture_type == "piCaptureSd1": + pass + #self.device.sensor_mode = 6 + #self.device.awb_mode = "off" + #self.device.awb_gains = 1.0 + #self.device.exposure_mode = "off" + else: + pass + #self.sensor_mode = 0 + + def stop_preview(self): + self.osc_client.send_message("/capture/preview/stop", True) + self.is_previewing = False + + def start_recording(self): + if self.use_capture == False: + self.message_handler.set_message('INFO', 'capture not enabled') + return + if not self.check_available_disk_space(): + return + + if not os.path.exists(self.video_dir): + os.makedirs(self.video_dir) + self.is_recording = True + #self.device.start_recording(self.video_dir + '/raw.h264') + self.osc_client.send_message("/capture/record/start", True) + self.monitor_disk_space() + + def monitor_disk_space(self): + if self.is_recording: + if self.check_available_disk_space(): + self.root.after(10000, self.monitor_disk_space) + else: + self.stop_recording() + + def check_available_disk_space(self): + mb_free = self.data._get_mb_free_diskspace(self.video_dir) + if mb_free < 10: + self.message_handler.set_message('INFO', 'insufficient space on disk') + return False + else: + return True + + def stop_recording(self): + #self.device.stop_recording() + self.osc_client.send_message("/capture/record/stop", True) + #set status to saving + mp4box_process, recording_name = self.convert_raw_recording() + self.is_recording = 'saving' + self.root.after(0, self.wait_for_recording_to_save, mp4box_process, recording_name) + + self.update_capture_settings() + # return path to the video + if not self.device.preview: + self.device.close() + + def convert_raw_recording(self): + recording_path , recording_name = self.generate_recording_path() + try: + if self.sensor_mode == 6: + mp4box_process = subprocess.Popen(['MP4Box', '-fps', '60', '-add', self.video_dir + '/raw.h264', recording_path]) + return mp4box_process , recording_name + else: + mp4box_process = subprocess.Popen(['MP4Box', '-add', self.video_dir + '/raw.h264', recording_path]) + return mp4box_process , recording_name + except Exception as e: + print(e) + if hasattr(e, 'message'): + error_info = e.message + else: + error_info = e + self.message_handler.set_message('ERROR',error_info) + + + def wait_for_recording_to_save(self, process, name): + print('the poll is {}'.format(process.poll())) + if process.poll() is not None: + self.is_recording = False + os.remove(self.video_dir + '/raw.h264') + self.data.create_new_slot_mapping_in_first_open(name) + else: + self.root.after(300, self.wait_for_recording_to_save, process, name) + + def generate_recording_path(self): + rec_dir = self.video_dir + '/recordings' + if not os.path.exists(rec_dir): + os.makedirs(rec_dir) + date = datetime.datetime.now().strftime("%Y-%m-%d") + i = 0 + while os.path.exists('{}/rec-{}-{}.mp4'.format(rec_dir,date, i)): + i += 1 + name = 'rec-{}-{}.mp4'.format(date, i) + return '{}/{}'.format(rec_dir,name), name + + def get_recording_time(self): + pass + #if not self.device or not self.device.recording or self.device.frame.timestamp == None: + # return -1 + #else: + # return self.device.frame.timestamp / 1000000 + + def get_preview_alpha(self): + return 0 + + def set_colour(self, u_value, v_value): + pass + + def set_alpha(self, amount): + pass + + @staticmethod + def convert_resolution_value(setting_value): + split_values = setting_value.split('x') + return (int(split_values[0]), int(split_values[1])) + + @staticmethod + def convert_framerate_value(setting_value): + return fractions.Fraction(setting_value).limit_denominator() + + def convert_shutter_value(self, setting_value): + if setting_value == 'auto': + return 0 + elif setting_value == 'max': + return int(1000000 / self.framerate) + else: + return int(fractions.Fraction(setting_value) * 1000000) + + +