def start_recording(self, path: str, filename: str) -> InternalOperationResult: if self.is_active(): self.__logger.error( 'Can\'t start FFMPEG for file %s: screen is acctually recording ', path + '/' + filename) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, 'Tablet is actually recording') try: self.__tablet_client.check_and_create_folder(path) except Exception as e: self.__logger.error('Error while creating remmote dir: %s', e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR) command = settings.FFMPEG_TABLET_CMD + path + '/' + filename + ' 2< /dev/null &' try: self.__tablet_client.execute_remote(command) except Exception as e: self.__logger.error( 'Screen recording start failed: %s; FFMPEG command: %s', e, command) return InternalOperationResult(ExecutionStatus.FATAL_ERROR) self.last_processed_file = filename self.last_processed_path = path self.__logger.info( 'Successfully start screen recording (FFMPEG command: %s', command) return InternalOperationResult(ExecutionStatus.SUCCESS)
def exec_and_get_output(self, command, stderr=None) -> (InternalOperationResult, str): try: return InternalOperationResult(ExecutionStatus.SUCCESS), \ subprocess.check_output(command, stderr=stderr) except Exception as e: logger.error('Can\'t get execution result of %s: %s', command, str(e)) return InternalOperationResult(ExecutionStatus.FATAL_ERROR), None
def stop_recording(self) -> InternalOperationResult: if not self.is_active(): self.__logger.warning( 'Tablet screencast isn\'t active: can\'t stop non existing FFMPEG process' ) return InternalOperationResult(ExecutionStatus.SUCCESS) command = 'pkill -f ffmpeg' for _ in range(0, ATTEMPTS_TO_STOP): try: # read_output=True is using for synchronized execution self.__tablet_client.execute_remote(command, allowable_code=1, read_output=True) except Exception as e: self.__logger.error('Problems while stop screen recording: %s', e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR) if not self.is_active(): return InternalOperationResult(ExecutionStatus.SUCCESS) else: time.sleep(ATTEMPTS_PAUSE) self.__logger.error( 'Can\'t stop screen recording process for %s seconds.', ATTEMPTS_PAUSE * ATTEMPTS_TO_STOP) return InternalOperationResult(ExecutionStatus.FIXABLE_ERROR)
def kill_process(pid, including_parent=True) -> InternalOperationResult: if not psutil.pid_exists(pid): InternalOperationResult(ExecutionStatus.SUCCESS) try: parent = psutil.Process(pid) except psutil.NoSuchProcess: return InternalOperationResult(ExecutionStatus.SUCCESS) except Exception as e: logger.error(str(e)) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, str(e)) for child in parent.children(recursive=True): try: child.kill() except Exception as e: logger.error('Can\'t kill process with pid %s (subprocess of %s) : %s', child.pid, pid, str(e)) if including_parent: try: parent.kill() except Exception as e: logger.error('Can\'t kill process with pid %s: %s', parent.pid, str(e)) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, str(e)) return InternalOperationResult(ExecutionStatus.SUCCESS)
def delete_recursively(self, path: str) -> InternalOperationResult: try: shutil.rmtree(path, ignore_errors=True) return InternalOperationResult(ExecutionStatus.SUCCESS) except Exception as e: logger.error('Can\'t delete recursively %s: %s', path, e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, e)
def rename_element_on_disk(from_obj: 'Step', to_obj: 'Step') -> InternalOperationResult: if os.path.exists(to_obj.os_path): message = 'File with name \'{0}\' already exists'.format(to_obj.name) logger.error(message) return InternalOperationResult(ExecutionStatus.FIXABLE_ERROR, message) if not os.path.exists(from_obj.os_path): # it may means that step created just now - it's OK return InternalOperationResult(ExecutionStatus.SUCCESS) if not os.path.isdir(from_obj.os_path): message = 'Cannot rename non-existent file: \'{0}\' doesn\'t exist'.format(from_obj.os_path) logger.error(message) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, message) try: os.rename(from_obj.os_path, to_obj.os_path) except Exception as e: message = 'Cannot rename element on disk: {0}'.format(str(e)) logger.exception('Cannot rename element on disk') return InternalOperationResult(ExecutionStatus.FATAL_ERROR, message) try: os.rename(from_obj.os_automontage_path, to_obj.os_automontage_path) except Exception as e: logger.exception('Cannot rename element on disk: %s', e) return InternalOperationResult(ExecutionStatus.SUCCESS)
def send_quit_signal(self, process) -> InternalOperationResult: try: process.stdin.write(bytes('q', 'UTF-8')) process.stdin.close() return InternalOperationResult(ExecutionStatus.SUCCESS) except Exception as e: logger.error('Can\'t send quit signal to process %s: %s', process.pid, e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR)
def get_storage_capacity(self, path: str) -> (InternalOperationResult, int): """Returns total disk capacity in byted.""" try: capacity = psutil.disk_usage(path=path).total return InternalOperationResult(ExecutionStatus.SUCCESS), capacity except Exception as e: logger.warning('Can\'t get information about total disk capacity: %s', str(e)) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, str(e)), None
def get_free_disk_space(self, path: str) -> (InternalOperationResult, int): """Returns free disk capacity in bytes.""" try: capacity = psutil.disk_usage(path=path).free return InternalOperationResult(ExecutionStatus.SUCCESS), capacity except Exception as e: logger.warning('Can\'t get information about disk free space: %s', str(e)) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, str(e)), None
def create_recursively(self, path: str) -> InternalOperationResult: try: if os.path.exists(path): return InternalOperationResult(ExecutionStatus.SUCCESS) os.makedirs(path, exist_ok=True, mode=777) return InternalOperationResult(ExecutionStatus.SUCCESS) except Exception as e: logger.error('Can\'t create dirs recursively: %s', e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR)
def delete_tablet_lesson_files(lesson) -> InternalOperationResult: try: client = TabletClient() client.delete_folder_recursively(lesson.tablet_path) client.close() return InternalOperationResult(ExecutionStatus.SUCCESS) except Exception as e: logger.warning('Failed to remove lesson %s from tablet (id: %s): %s', lesson.name, lesson.id, e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR)
def delete_file(self, path: str) -> InternalOperationResult: if not self.__is_alive(): self.__connect() try: if not self.__is_exists(path)[0]: return InternalOperationResult(ExecutionStatus.SUCCESS) self.__sftp.remove(path) return InternalOperationResult(ExecutionStatus.SUCCESS) except Exception as e: self.__logger.error('Can\'t delete file %s on tablet: %s', path, e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR)
def execute_command_sync(command, allowable_code=0) -> InternalOperationResult: """Blocking execution.""" try: # raise exception when returncode != 0 subprocess.check_call(command, shell=True) return InternalOperationResult(ExecutionStatus.SUCCESS) except subprocess.CalledProcessError as e: if e.returncode == allowable_code: return InternalOperationResult(ExecutionStatus.SUCCESS) logger.error('Cannot exec command: %s', str(e)) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, 'Cannot exec command: {0}'.format(str(e)))
def remove_file(file: str) -> InternalOperationResult: if not os.path.isfile(file): logger.warning('Can\'t delete non-existing file %s ', file) return InternalOperationResult(ExecutionStatus.SUCCESS) try: os.remove(file) except Exception as e: logger.warning('Can\'t delete %s :', e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR) return InternalOperationResult(ExecutionStatus.SUCCESS)
def download_file(self, remote_dir, filename, local_dir) -> InternalOperationResult: try: if not self.__is_alive(): self.__connect() os.path.exists(local_dir) or os.makedirs(local_dir) self.__sftp.get(remote_dir + '/' + filename, os.path.join(local_dir, filename)) return InternalOperationResult(ExecutionStatus.SUCCESS) except Exception as e: self.__logger.error('Can\'t download remote file %s: %s', remote_dir + '/' + filename, e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, e)
def execute_command(self, command: str, stdout=None, stdin=None) -> (InternalOperationResult, subprocess.Popen): try: proc = subprocess.Popen(command, shell=True, stdout=stdout, stdin=stdin) # process still running when returncode is None if proc.returncode is not None and proc.returncode != 0: _, error = proc.communicate() message = 'Cannot exec command: (return code: {0}): {1}'.format(proc.returncode, error) logger.error(message) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, message), proc else: return InternalOperationResult(ExecutionStatus.SUCCESS), proc except Exception as e: message = 'Cannot exec command: {0}'.format(str(e)) logger.exception('Cannot exec command: ') return InternalOperationResult(ExecutionStatus.FATAL_ERROR, message), None
def sync(self, screen_path, camera_path) -> InternalOperationResult: screen_path = os.path.splitext(screen_path)[0] + '.mp4' camera_path = os.path.splitext(camera_path)[0] + '.mp4' if not os.path.isfile(screen_path) or not os.path.isfile(camera_path): self.__logger.warning('Invalid paths to videos: (%s; %s)', screen_path, camera_path) return InternalOperationResult(ExecutionStatus.FATAL_ERROR) try: duration_1 = self.__get_silence_duration( screen_path, self.__tablet_noise_tolerance, self.__min_silence_duration) duration_2 = self.__get_silence_duration( camera_path, self.__camera_noise_tolerance, self.__min_silence_duration) except Exception: self.__logger.warning('Can\'t get silence duration of %s, %s.', screen_path, camera_path) return InternalOperationResult(ExecutionStatus.FATAL_ERROR) longer = '' try: silence_diff = self.__get_valid_silence_diff( duration_1, duration_2) if silence_diff == 0: self.__logger.info( 'Video synchronizing: no need to be synchronized, silence difference < %ssec.', self.__min_diff) return InternalOperationResult(ExecutionStatus.SUCCESS) if silence_diff > 0: longer = screen_path self.__add_empty_frames(camera_path, silence_diff) elif silence_diff < 0: longer = camera_path self.__add_empty_frames(screen_path, abs(silence_diff)) except Exception as e: self.__logger.warning('Invalide difference: %s', e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR) self.__logger.info( 'Videos successfully synchronized (difference: %s sec.; longer silence in %s; ' 'silence duration of %s - %s sec.; silence duration of %s - %s sec.)', '%.3f' % silence_diff, longer, screen_path, duration_1, camera_path, duration_2) return InternalOperationResult(ExecutionStatus.SUCCESS)
def raw_cut(self, substep_id: int) -> InternalOperationResult: substep = SubStep.objects.get(pk=substep_id) if substep.automontage_exist: return InternalOperationResult(ExecutionStatus.SUCCESS) screen_full_path = substep.os_screencast_path prof_full_path = substep.os_path output_dir = substep.os_automontage_path status = self._fs_client.create_recursively(output_dir) if status.status is not ExecutionStatus.SUCCESS: return status full_output = os.path.join( output_dir, substep.name + RAW_CUT_LABEL + MP4_EXTENSION) substep.is_locked = True substep.save() internal_status = self._internal_raw_cut(screen_full_path, prof_full_path, full_output) substep = SubStep.objects.get(pk=substep_id) substep.is_locked = False substep.save() return internal_status
def start_recording(substep) -> InternalOperationResult: create_status = FileSystemClient().create_recursively(substep.dir_path) if create_status.status is not ExecutionStatus.SUCCESS: logger.error('Can\'t create folder for new substep: %s', create_status.message) return create_status tablet_exec_info = TabletScreenRecorder().start_recording( substep.os_tablet_dir, substep.screencast_name) if tablet_exec_info.status is not ExecutionStatus.SUCCESS: return tablet_exec_info ffmpeg_status = ServerCameraRecorder().start_recording( substep.dir_path, substep.camera_recording_name) if ffmpeg_status.status is not ExecutionStatus.SUCCESS: TabletScreenRecorder().stop_recording() return ffmpeg_status db_camera = CameraStatus.objects.get(id='1') if not db_camera.status: db_camera.status = True db_camera.start_time = int(round(time.time() * 1000)) db_camera.save() return InternalOperationResult(ExecutionStatus.SUCCESS)
def start_recording(self, path: str, filename: str) -> InternalOperationResult: if self.is_active(): self.__logger.error( 'Can\'t start FFMPEG for file %s: camera is acctually recording (process with PID %s)', os.path.join(path, filename), self.__process.pid) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, 'Camera is actually recording') local_command = self.__command + os.path.join(path, filename) result, self.__process = self.__fs_client.execute_command( local_command, stdin=subprocess.PIPE) if result.status is ExecutionStatus.SUCCESS: self.__logger.info( 'Successfully start camera recording (FFMPEG PID: %s; FFMPEG command: %s)', self.__process.pid, local_command) self.last_processed_file = filename self.last_processed_path = path else: self.__logger.error( 'Camera recording start failed: %s; FFMPEG command: %s', result.message, local_command) return result
def stop_recording(self) -> InternalOperationResult: if not self.is_active(): if self.__process is None: pid = None else: pid = self.__process.pid self.__process = None self.__logger.warning( 'Camera isn\'t active: can\'t stop non existing FFMPEG process ' '(try to stop process with PID %s)', pid) return InternalOperationResult( ExecutionStatus.SUCCESS, 'Camera isn\'t active: can\'t stop non existing FFMPEG process' ) result = self.__fs_client.send_quit_signal(self.__process) # try to kill ffmpeg process (sending of quit signal may have no effect) TaskManager().run_with_delay(FileSystemClient.kill_process, [self.__process.pid], delay=KILL_DELAY) if result.status is ExecutionStatus.SUCCESS: self.__logger.info( 'Successfully stop camera recording (FFMPEG PID: %s)', self.__process.pid) self.__process = None self._apply_pipe(self.last_processed_path, self.last_processed_file) return result else: self.__logger.error( 'Problems while stop camera recording (FFMPEG PID: %s) : %s', self.__process.pid, result.message) return result
def delete_folder(self, path: str) -> (InternalOperationResult, int): if not self.__is_alive(): self.__connect() status, size = self.__is_exists(path) if status is False: return InternalOperationResult(ExecutionStatus.SUCCESS), 0 try: files = self.__sftp.listdir(path=path) size = 0 for f in files: size += self.get_file_size(path + '/' + f) self.__sftp.remove(path + '/' + f) self.__sftp.rmdir(path) return InternalOperationResult(ExecutionStatus.SUCCESS), size except Exception as e: self.__logger.error('Can\'t delete folder %s: %s', path, e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR), 0
def download_dir(self, remote_dir, local_dir) -> InternalOperationResult: try: if not self.__is_alive(): self.__connect() os.path.exists(local_dir) or os.makedirs(local_dir) dir_items = self.__sftp.listdir_attr(remote_dir) for item in dir_items: remote_path = remote_dir + '/' + item.filename local_path = os.path.join(local_dir, item.filename) if S_ISDIR(item.st_mode): status = self.download_dir(remote_path, local_path) if status.status is not ExecutionStatus.SUCCESS: return status else: self.__sftp.get(remote_path, local_path) return InternalOperationResult(ExecutionStatus.SUCCESS) except Exception as e: self.__logger.error('Can\'t download remote directory %s: %s', remote_dir, e) return InternalOperationResult(ExecutionStatus.FATAL_ERROR, e)
def delete_substep_on_disk(substep) -> InternalOperationResult: client = FileSystemClient() cam_removing_info = client.remove_file(substep.os_path) if cam_removing_info.status is not ExecutionStatus.SUCCESS: return cam_removing_info screencast_removing_info = client.remove_file(substep.os_screencast_path) if screencast_removing_info.status is not ExecutionStatus.SUCCESS: return screencast_removing_info raw_cut_removing_info = client.remove_file(substep.os_automontage_file) if raw_cut_removing_info.status is not ExecutionStatus.SUCCESS: return raw_cut_removing_info return InternalOperationResult(ExecutionStatus.SUCCESS)
def _internal_raw_cut(self, video_path1: str, video_path2: str, output_path: str) -> InternalOperationResult: status_1 = os.path.isfile(video_path1) status_2 = os.path.isfile(video_path2) if not status_1 or not status_2: self.__logger.warning('Can\'t process invalid videos: %s, %s', video_path1, video_path2) return InternalOperationResult(ExecutionStatus.FATAL_ERROR) command = settings.FFMPEG_PATH + ' ' + \ settings.RAW_CUT_TEMPLATE.format(video_path1, video_path2, output_path) status = self._fs_client.execute_command_sync(command) if status.status is not ExecutionStatus.SUCCESS: return status
def delete_folder_recursively(self, path: str): if not self.__is_alive(): self.__connect() status, _ = self.__is_exists(path) if status is False: return InternalOperationResult(ExecutionStatus.SUCCESS) files = self.__sftp.listdir(path=path) for f in files: filepath = path + '/' + f try: self.__sftp.remove(filepath) except IOError: self.delete_folder_recursively(filepath) self.__sftp.rmdir(path)
def export_obj_to_prproj(db_object, files_extractor) -> InternalOperationResult: """Creates PPro project in .prproj format using ExtendScript script. Project includes video files of each subitem of corresponding object. Screencasts and camera recordings puts on different tracks of single sequence. :param files_extractor: function for extracting target filenames from db_object; :param db_object: db single object. """ ppro_dir = os.path.dirname(settings.ADOBE_PPRO_PATH) if not os.path.isfile(os.path.join(ppro_dir, PRPROJ_REQUIRED_FILE)): return InternalOperationResult( ExecutionStatus.FATAL_ERROR, '\'{0}\' is missing. Please, place \'{0}\' empty file to \n\'{1}\'.' .format(PRPROJ_REQUIRED_FILE, ppro_dir)) if FileSystemClient().process_with_name_exists(PPRO_WIN_PROCESS_NAME): return InternalOperationResult( ExecutionStatus.FATAL_ERROR, 'Only one instance of PPro may exist. Please, close PPro and try again.' ) screen_files, prof_files, marker_times, sync_offsets = files_extractor( db_object) if not screen_files or not prof_files: return InternalOperationResult( ExecutionStatus.FATAL_ERROR, 'Object is empty or subitems are broken.') try: ppro_command = build_ppro_command( db_object.os_path, PRPROJ_TEMPLATES_PATH, screen_files, prof_files, marker_times, sync_offsets, translate_non_alphanumerics(db_object.name)) except Exception as e: return InternalOperationResult(ExecutionStatus.FATAL_ERROR, e) exec_status = FileSystemClient().execute_command_sync( ppro_command, allowable_code=1) # may return 1 - it's OK if exec_status.status is not ExecutionStatus.SUCCESS: logger.error('Cannot execute PPro command: %s \n PPro command: %s', exec_status.message, ppro_command) return InternalOperationResult( ExecutionStatus.FATAL_ERROR, 'Cannot execute PPro command. Check PPro configuration.') logger.info('Execution of PPro command started; \n PPro command: %s', ppro_command) return InternalOperationResult(ExecutionStatus.SUCCESS)